预备知识
ES6函数的扩展
1.函数参数的默认值
ES6之前不能直接对函数的参数指定默认值。对参数y赋值:
y = y || expression
但是如果expression的结果为false,该赋值不起作用。为了避免这个问题,可以先判断参数y是否赋值了
if (typeof y === 'undefined') {
y = 'World';
}
ES6允许直接在参数定义时,给参数赋默认值
function fun(x, y = 'hello'){
...
}
[Note]
- 参数变量是默认声明的,所以不能用let或const再次声明。
- 使用参数默认值时,函数不能有同名参数。
- 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
与解构赋值默认值结合使用(待补充)
默认参数的位置
一般有默认值的参数放在参数中的尾部。如果不在尾部,要传入该参数的默认值,需要在此位置显示传入undefined
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
函数的length属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
这是因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。
(function(...args) {}).length // 0
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(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
如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。
let foo = 'outer';
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar(); // outer
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
2.rest参数
syntax:
…variableName
e.g.
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
[Note]
- rest参数之后不能再有其他参数。
- 函数的length属性不包括rest参数。
3.严格模式
ES6规定,只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
4.name属性
返回该函数的实际函数名。
function foo() {}
foo.name // "foo"
var f = function () {};
// f是一个匿名函数
// 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 "
5.箭头函数
使用箭头(=>)定义函数,var f = v => v;
等价于
var f = function(v){
return v;
}
箭头函数可以与变量解构结合使用
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
简化回调函数
// 正常函数写法
[1,2,3].map(function (x) {
return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);
[Note]箭头函数
- 函数体内this对象指向定义时所在的对象
- 不可以当做构造函数(不能使用new命令)
- 不可以使用arguments对象
- 不可以使用yield命令
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
上面例子中,setTimeout的参数是一个箭头函数,这个箭头函数定义生效是在foo函数生成时,所以箭头函数里面的this指向foo对象。如果是setTimeout的参数是普通函数,真正执行的时候应该在100毫秒之后,this指向全局对象window。
箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域。
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数 this绑定Timer函数
setInterval(() => this.s1++, 1000);
// 普通函数 this指向运行时的作用域(全局对象)
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 4100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// 注意s2会比s1先被打印出来
// s2: 0
// s1: 4
为什么箭头函数的this会绑定定义时所在的对象呢?因为它根本没有自己的this,而是引用外层的this o( ̄︶ ̄)o
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
除了this,箭头函数中也不存在arguments、super、new.target,使用的时候,指向的是外层函数对应变量。
因为没有自己的this,不能用call()、apply()、bind()这些方法去改变this指向。
未完待续。。。