函数的扩展
基本用法
ES6 之前不能为函数的参数指定默认值:
const hello = (name) => {
name = name || 'World';
return `Hello ${name}`;
}
log(hello()); // 'Hello World'
log(hello('China')); // 'Hello China'
// 缺点:如果参数赋值了,但是对应的布尔值是 false,则赋值不起作用
log(hello('')); // 'Hello World'
log(hello(false)); // 'Hello World'
为了避免上面这个问题,通常需要判断参数是否被赋值。
const hello = (name) => {
if (typeof name === 'undefined') {
name = 'World';
}
return `Hello ${name}`;
}
log(hello()); // 'Hello World'
log(hello('China')); // 'Hello China'
log(hello('')); // 'Hello '
log(hello(false)); // 'Hello false'
ES6 的语法:
const hello = (name = 'World') => {
return `Hello ${name}`;
};
log(hello()); // 'Hello World'
log(hello('China')); // 'Hello China'
log(hello('')); // 'Hello '
log(hello(false)); // 'Hello false'
函数参数是默认声明的,所以不能再用 const 或 let 声明。
使用参数默认值时,不能有同名参数。
const hello = (name = 'World', name) => {
return `Hello ${name}`;
};
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
参数默认值不是传值的,而是每次都重新计算默认值表达式的值。
let x = 10;
const calculate = (a = x + 1) => {
return a;
};
log(calculate(x)); // 10
x = 11;
log(calculate(x)); // 11
与解构赋值默认值结合使用
const foo = ({x, y = 1}) => {
console.log(x, y);
};
foo({}); // undefined 1
foo({x: 1, y: 2}); // 1 2
foo({x: 1}); // 1 1
foo(); // Uncaught TypeError: Cannot destructure property `x` of 'undefined' or 'null'.
提供函数参数的默认值,就不会报错。
const foo = ({x, y = 1} = {}) => {
console.log(x, y);
};
foo(); // undefined 1
两种写法的差别:
const foo1 = ({x = 1, y = 2} = {}) => {
console.log(x, y);
};
const foo2 = ({x, y} = {x: 1, y: 2}) => {
console.log(x, y);
};
foo1(); // 1 2
foo2(); // 1 2
foo1({x: 3, y: 4}); // 3 4
foo2({x: 3, y: 4}); // 3 4
foo1({x: 3}); // 3 2
foo2({x: 3}); // 3 undefined
foo1({}); // 1 2
foo2({}); // undefined undefined
参数默认值的位置
有默认值的参数不是尾参数,不能只省略该参数,不省略后面的参数。
const foo = (x = 1, y) => {
console.log(x, y);
};
foo(); // 1 undefined
foo(2); // 2 undefined
foo(undefined, 1); // 1 1
foo(, 2); // Uncaught SyntaxError: Unexpected token ,
传入 undefined 可以触发参数的默认值,null 不会。
foo(null, 1); // null 1
函数的 length 属性
返回没有指定默认值的参数的个数。
const foo = (x, y) => {};
log(foo.length); // 2
const foo = (x, y = 1) => {};
log(foo.length); // 1
length 的含义是该函数预期传入的参数个数,指定默认值后,预期传入的参数个数就不包括这个参数了。
rest 参数也不会计入参数个数。
const foo = (...rest) => {};
log(foo.length); // 0
如果设置的默认值参数不是尾参数,那么其后面的参数也不会计入参数个数。
const foo = (x = 1, y) => {};
log(foo.length); // 0
作用域
设置了参数默认值,函数只声明初始化时,参数会形成一个单独的作用域,等到初始化结束后,这个作用域会消失。在不设置默认值时,不出现这个作用域。
const x = 1;
const foo = (x, y = x) => { // <-- 在单独的作用域中,y 指向的是这个作用域中的 x,即 2
console.log(y);
};
foo(2); // 2
const x = 1;
const foo = (y = x) => {
console.log(y);
};
foo(); // 1 如果未传入 x,会指向全局的变量
全局变量 x 不存在,会报错。
const foo = (y = x) => {
console.log(y);
};
foo(); // Uncaught ReferenceError: x is not defined
const x = 1;
const foo = (x = x) => {
console.log(x);
};
foo(2); // 2
const x = 1;
const foo = (x = x) => {
console.log(x);
};
foo(); // 暂时性死区 demo.js:143 Uncaught ReferenceError: Cannot access 'x' before initialization
应用
利用参数默认值,可以指定某个参数不可省略,如果省略就抛出错误。
const throwIfMissing = () => {
throw new Error('Missing parameter.');
};
const foo = (name = throwIfMissing()) => {
return name;
};
foo(); // Uncaught Error: Missing parameter.
rest 参数
用于获取函数的剩余参数。
const sum = (...values) => {
return values.reduce((accumulator, current) => {
return accumulator + current;
}, 0);
};
console.log(sum(1, 2, 3)); // 6
rest 参数代替 arguments 变量:
const sum = (...values) => {
return values.sort();
};
rest 参数后不能再有其他参数,否则会报错。
严格模式
只要函数参数使用了默认值,解构赋值或扩展运算符,函数内部就不能显示设定为严格模式。
略...
name 属性
返回函数的名称。
log((function sum(){}).name); // sum
匿名函数:
log((new Function).name); // anonymous
bind 返回的函数,name 属性会加上 bound 前缀。
const foo = () => {};
log(foo.bind({}).name); // bound foo
箭头函数
基本用法
const foo = () => 5;
const sum = (a, b) => a + b;
如果代码块部分多于一条语句,要使用大括号括起来,使用 return 语句返回。
const sum = (a, b) => { return a + b };
由于大括号被解释为代码块,如果箭头函数返回一个对象,必须在对象外面加(),否则报错。
// const foo = () => {a: 1, b: 2}; // Uncaught SyntaxError: Unexpected token :
const foo = () => ({a: 1, b: 2});
特殊情况:
const foo = () => {a: 1}; // 不报错
log(foo()); // undefined 结果错误
箭头函数可以与变量解构结合使用。
const name = ({firstName, lastName}) => `${firstName} ${lastName}`;
log(name({firstName: 'Wendy', lastName: 'Lu'})); // Wendy Lu
rest 参数与箭头函数结合。
const numbers = (...nums) => nums;
log(numbers(1, 2, 3)); // [1, 2, 3]
使用注意点
- 函数体内的 this,是定义时所在的对象,不是使用时所在的对象。
- 不可以当作构造函数使用,即不可使用 new 命令
- 不可以使用 arguments 对象,在函数体内不存在
- 不可以使用 yield 命令,因此箭头函数不可以用作 Generator 函数。
在箭头函数中,this 对象的指向是固定的。
function foo() {
setTimeout(function () {
console.log(this.id);
}, 100);
}
var id = 'global';
foo.call({ id: 'foo' }); // 'global', this 指向全局对象
function foo2() {
setTimeout(() => {
console.log(this.id);
}, 100);
}
foo2.call({ id: 'foo' }); // 'foo',this 指向定义生效时的对象
箭头函数让 this 指向固定化,有利于封装回调函数。
const handler = {
id: '11',
init() {
document.addEventListener('click', event => {
console.log(this, 1); // handler
this.doSomething(event.type);
}, false);
},
doSomething(type) {
console.log(`Handling ${type} for ${this.id}`);
}
};
handler.init();
const handler2 = {
id: '22',
init() {
document.addEventListener('click', function(event) {
console.log(this, 2); // document
this.doSomething(event.type);
}, false);
},
doSomething(type) {
console.log(`Handling ${type} for ${this.id}`);
}
};
handler2.init(); // Uncaught TypeError: this.doSomething is not a function
arguments、super、new.target 在箭头函数中不存在,指向外层函数。
function foo() {
setTimeout(() => {
console.log(arguments);
}, 100);
}
foo(1, 2, 3); // [1, 2, 3]
箭头函数没有 this,所以不能用 bind、call、apply 去改变 this 的指向。
const result = (function() {
return (() => this.name).bind({name: 'inner'})();
}).call({name: 'outer'});
log(result); // 'outer'
不适用的场合
const cat = {
lives: 9,
jumps: () => {
console.log(this);
}
};
cat.jumps(); // this 指向 window
需要动态 this 的时候:
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
console.log(this); // window
});
上面代码需要动态指向被点击的对象。
嵌套的箭头函数
function insert(value) {
return {into: function (array) {
return {after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}};
}};
}
用箭头函数改造:
const insert = (value) => ({into: (array) => ({after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}})});
log(insert(4).into([1, 2, 3, 5]).after(3)); // [1, 2, 3, 4, 5]
部署管道机制--前一个函数的输出是后一个函数的输入。
const pipe = (...functions) =>
(val) => functions.reduce((accumulator, current) => current(accumulator), val);
const add = (a) => a + 1;
const multiply = (a) => a * 2;
log(pipe(add, multiply)(1)); // 4
尾调用优化
尾调用就是函数的最后一步是调用另外一个函数。
function bar(a) {}
function foo(a) {
return bar(a);
}
下面三种情况不是尾调用:
function bar(a) {}
// 第一种
function foo(a) {
const x = bar(a);
return x;
}
// 第二种
function foo2(a) {
return bar(a) + 1;
}
// 第三种
function foo3(a) {
bar(a);
}
尾递归
尾调用自身,就叫尾递归。
略...