ES6 函数的扩展
1. 函数参数的默认值
ES6之前不能直接为函数的参数指定默认值,只能通过变通的方法;
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello'); // Hello World
log('Hello', 'xhk'); // Hello xhk
log('Hello', ''); // Hello World
ES6允许函数为参数设置默认值, 即直接写在参数定义的后面;
function log(x, y = 'World') {
console.log(x, y);
}
log('coco'); // coco World
log('coco', 'xhk'); // coco xhk
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // {x:0, y: 0}
参数声明时默认声明的, 所以不能用let, const再次声明。
与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值结合使用。
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // Uncaught TypeError: Cannot destructure property `x` of 'undefined' or 'null'.
function foo({x, y = 5} = {}) {
console.log(x, y);
}
如果没有提供参数, 函数foo的参数默认为一个空对象。
foo() // undefined 5
function fetch(url, {body = '', method = 'GET', headers = {}}) {
console.log(method);
}
fetch('http://example.com', {}) // GET
fetch('http://example.com') // 报错
function fetch(url, {body = '', method = 'GET', headers = {}} = {}) {
console.log(method, headers);
}
fetch('http://example.com') // GET {}
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
m1() // [0, 0]
m1({}) // [0, 0]
m1({x: 2, y: 3}) // [2, 3]
m1({y: 3}) // [0, 3]
function m2({x, y} = {x: 0, y: 0}) {
return [x, y]
}
m2() // [0, 0]
m2({}) // [undefined, undefined]
m2({x: 1}) // [1, undefined]
m2({y: 1}) // [undefined, 1]
m2({x: 1, y: 1}) // [1, 1]
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(2, 2) // [2, 2]
f(, 2) // 报错
f(undefined, 2) // [1, 2]
function f(x, y= 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, 2) // [1, 2, undefined]
f(1, undefined, 3) // [1, 5, 3]
上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined
如果传入undefined
,将触发该参数等于默认值,null
则没有这个效果
function foo(x= 5, y = 3) {
console.log(x, y);
}
foo(undefined, null) // 5, null
上面代码中,x
参数对应undefined
,结果触发了默认值,y
参数等于null
,就没有触发默认值
函数的length属性
指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真
(function (a){}).length // 1
(function (a, b){}).length // 2
(function (a = 0, b){}).length // 0
(function (a, b = 2, c){}).length // 1
(function (a, b, c = 2){}).length // 2
这是因为length
属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length
属性
(function(...args){}).length // 0
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1;
function f (x, y = x) {
console.log(y);
}
f(2) // 2
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
function f(y = x) {
let x = 2;
console.log(y);
}
f(1) // 1
f() // 报错
var x = 1;
function foo(x = x) {
}
foo() // x is not defined 暂时性死区
let foo = 'outer';
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar() // outer
function bar(func = () => foo) {
foo = 'inner';
console.log(func());
}
bar() // inner
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar() // foo is not defined
var x = 1;
function foo(x, y = function() {x = 2;}) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
function foo(x, y = function() {x = 2;}) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
可以将参数默认值设为undefined
,表明这个参数是可以省略的。
rest参数 形式为 ...变量名
用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function push(array, ...items) {
items.forEach(function(item){
array.push(item);
console.log(item);
})
}
var arr= [];
push(arr, 1, 2, 3);
arr // [1, 2, 3]
rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
function f(a, ...b, c) {
console.log(a, ...b, c);
} // Uncaught SyntaxError: Rest parameter must be last formal parameter
函数的length
属性,不包括 rest 参数。
(function(...a) {}).length // 0
(function(a) {}).length // 1
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 使用默认值
function doSomething(a, b = a) {
'use strict';
}
// Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
// 使用结构赋值
const doSomething = function({a, b}) {
'use strict';
}
// 扩展运算符
const doSomething = (...a) => {
'use strict';
}
const obj = {
doSomething({a, b}) {
'use strict';
}
}
// Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
function doSomething(value = 070) {
'use strict';
return value;
} // Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
严格模式下不能用前缀0
表示八进制,所以应该报错。
两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。
'use strict';
function doSomething(a, b = a) {
}
第二种是把函数包在一个无参数的立即执行函数里面。
const doSomething = (function(){
'use strict';
return function(value = 42) {
return value;
}
}());
Name属性 函数的name
属性,返回该函数的函数名。
function xhk() {}
xhk.name // 'xhk'
var f = function () {}
es5返回空字符串
f.name // ''
es6返回实际函数名
f.name // 'f'
Function
构造函数返回的函数实例,name
属性的值为anonymous
。
(new Function).name // "anonymous"
bind
返回的函数,name
属性值会加上bound
前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
箭头函数
ES6 允许使用“箭头”(=>
)定义函数。
var f = v => v;
等同于
var f = function (v) {
return v;
}
如果箭头函数不需要参数或者需要多个参数,使用一个圆括号代替参数部分
var sum = (num1, num2) => num1 + num2
等同于
var sum = function(num1, num2) {
return num1 + num2;
}
如果箭头函数的代码块部分多于一句语句,就要使用大括号将它们括起来, 并且用return语句返回
var sum = (num1, num2) => {return num1 + num2}
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上一个括号,否则会报错。
let getTempItem = id => {id: id, name: 'Temp'}; // 报错 Uncaught SyntaxError: Unexpected token :
let getTempItem = id => ({id: id, name: 'Temp'}); // 不报错
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
var f = () => console.log('f');
const full = (first, last) => first + ' ' + last
full('xhk, 'coco') // xhk coco
等同于
function full(person) {
return person.husband + ' ' + person.wife
}
const isEven = n => n % 2 == 0
isEven (6) // true
isEven (5) // false
const square = n => n * n
square(6) // 36
箭头函数的一个用处是简化回调函数
[1, 2, 3].map(x => x * x); // [1, 4, 9]
var res = values.sort((a, b) => a-b);
res // [1, 2, 3]
const numbers = (...nums) => nums
numbers(1, 2, 3, 4, 5) // [1, 2, 3, 4, 5]
const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1, 2, 3, 4, 5); // [1, [2, 3, 4, 5]]
箭头函数有几个使用注意点
1)函数内的this对象, 是定义时所在的对象, 而不是使用时所在的对象。
2)不能当做构造函数, 也就是不可以使用new命令,否则会抛出一个错误。
3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
4)不可以使用yield
命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this
对象的指向是可变的,但是在箭头函数中,它是固定的。
箭头函数可以让setTimeout
里面的this
,绑定定义时所在的作用域,而不是指向运行时所在的作用域。
this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
除了this
,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.target
function foo() {
setTimeout(() => {
console.log('args: ', arguments);
}, 1000);
}
foo(2, 4, 6, 8) // [2, 4, 6, 8]
由于箭头函数没有自己的this
,所以当然也就不能用call()
、apply()
、bind()
这些方法去改变this
的指向。
箭头函数不能使用call apply bind这些方法改变this的指向
嵌套的箭头函数
箭头函数内部,还可以再使用箭头函数。
双冒号运算符
函数绑定运算符是并排的两个冒号(::
),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this
对象),绑定到右边的函数上面。
foo::bar 等同于 bar.bind(foo)
foo::bar(...arguments) 等同于 bar.apply(foo, arguments)
如果双冒号左边为空,右边为一个对象的方法,默认该方法绑定在该对象上。
var method = obj :: obj.foo;
等同于
var method = :: obj.foo;
var method = ::console.log;
等同于
var method = console.log.bind(console);
尾调用
指某个函数的最后一步是调用另一个函数
尾调用不一定出现在函数尾部,只要是最后一步操作即可。
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。
严格模式
es6的尾调用优化只在严格模式下开启,正常模式下是无效的。