箭头函数是指通过箭头函数表达式创建的函数,是匿名函数。
箭头函数表达式的语法更简洁,但语义有差异,所以用法上也有一些限制。尽管如此,箭头函数依旧被广泛运用在需要执行“小函数”的场景。
箭头函数表达式的基本语法:
/* 函数体只有一个语句 */
() => 单个语句
单个参数 => 单个语句
(单个参数) => 单个语句
(参数1, 参数2) => 单个语句
let f = ()=>console.log(111)
f() === undefined // true
/* 函数体有多个语句,使用{} */
() => { 多个语句 }
单个参数 => { 多个语句 }
(单个参数) => { 多个语句 }
(参数1, 参数2) => { 多个语句 }
let g = ()=>{}
箭头函数表达式的语法说明:
()
可以省略;{}
可以省略,返回值就是该语句/表达式的值;{}
包围;()
;=>
的优先级不高,在符合运算中尽量用 ()
包围。let f = ()=>{ a:1 } // 不报错,a被当成一个标签,执行了表达式 “1”,返回 undefined
let f = ()=>{ a: 1, b: 2 } // 报错
let f = ()=>{ a: 1; b: 2 } // 不报错,a,b都时标签
let f = ()=>( {a:1, b:2} ) // 不报错,()改变了优先级
let g = () = > {} // 报错
let g = () // 报错
=> {}
let x = f || ( ()=>{} )
箭头函数的限制:
this
,会捕获其声明所在上下文的 this
,且 call/apply/bind
方法都不会改变其指向;new
调用,会引发类型错误,函数体内也无法访问 new.target
;yield
,不能用做生成器函数;arguments
对象,可以使用剩余参数取而代之;prototype
这个属性。箭头函数没有独立的 this
,下面将依次总结其如何绑定上下文的 this
、避免用作方法等限制问题。
箭头函数捕获声明所在上下文的 this
并绑定,考虑捕获绑定顺序如下:
this
绑定;this
上下文,箭头函数作为方法时,将自动绑定实例的 this
;本质就像闭包,有独立的变量环境,不会因为属性解构等操作改变其绑定。globalThis
绑定。由于箭头函数会绑定外部函数的 t h i s this this,所以在一些需要访问外部属性时常用,诸如函数 setTimeout/setInterval
、方法 forEach/map
等。下面一个例子中:
showGroup2
内部的箭头函数绑定的是外部函数的 this
,指向 w b 对象 wb对象 wb对象,所以能够访问其属性 group
;showGroup3
绑定的是全局(globalThis
),指向 全局对象 全局对象 全局对象,访问的是全局属性;convert
内部的箭头函数绑定的是 this.data 的 this
,指向 w b 对象 wb对象 wb对象,能够正确访问属性 group
;convert2
内部的箭头函数绑定的也是 this.data 的 this
,但外部也是箭头函数,指向 全局对象 全局对象 全局对象,将会报错或者得到错误的结果,所以尽量不使用箭头函数作为方法。const wb = {
group: '第一组',
data: [1, 2, 3, 4],
showGroup: function () { console.log(this.group); },
showGroup2: function () { (() => { console.log(this.group) })(); },
showGroup3: () => { console.log(this.group) },
convert: function() {
let res = new Array;
this.data.forEach( (elem)=>{ res.push(this.group + ':' + elem); } );
return res;
},
convert2: () => {
let res = new Array;
// console.log(this == globalThis), // true
// console.log(this.data); // undefined/其他
this.data.forEach( (elem)=>{ res.push(this.group + ':' + elem); } );
return res;
},
}
类的封闭性使得箭头函数与实例自动绑定,每创建一个实例,都分配一个闭包,和实例环境自动绑定。下面一个例子中:
fa
绑定具体的实例对象,解构赋值不会改变 this 指向;fd
在构造时绑定自身,解构赋值也不会改变 this 指向;fb/fc
作为普通方法,在构造时没有绑定自身,解构赋值会改变 this 指向。class C {
constructor() { this.fd = this.fd.bind(this) };
a = 1;
fa = ()=>{ console.log(this.a); };
fb = function () { console.log(this.a); };
fc () { console.log(this.a); };
fd = function () { console.log(this.a); };
}
let c = new C();
c.fa(); // 1
let { fa, fb, fc, fd } = c;
fa(); // 1
fb(); // 报错,属性未定义
fc(); // 报错,属性未定义
fd(); // 1
箭头函数表达式是执行完创建的,所以 this 绑定声明定义所在上下文的 this。call
、bind
和 apply
方法都不会改变其指向。下面一个例子中:
this
绑定,可以根据需求被 call/apply/bind
改变,指向 o o o 对象;this
绑定,不可以被 call/apply/bind
改变,依旧指向全局对象。const o = { a: 10 }
globalThis.a = 100;
const add = function (m, n) { return this.a + m + n; }
const add2 = n => this.a + m + n;
add.call(o, 2, 3) // 15
add.apply(o, [2, 3]) // 15
let boundedAdd = add.bind(o);
boundedAdd(2, 3) // 15
add2.call(o, 2, 3) // 105
add2.apply(o, [2, 3]) // 105
let boundedAdd2 = add.bind(o);
boundedAdd2(2, 3) // 105
setTimeout
函数,使用一定封闭环境内的属性时,常用箭头函数作为回调函数。下面一个例子中:
loading
中箭头函数,绑定外部方法 loading
的 this
,指向 o 对象 o对象 o对象;loading2
中由 function
引导的声明式函数,将绑定全局(globalThis
),指向 全局对象 全局对象 全局对象;const o = {
n: 0,
loading () {
setTimeout(() => {
console.log(++this.n); // 箭头函数,this绑定声明所在上下文
}, 1000)
},
loading2 () {
setTimeout(function () {
console.log(++this.n); // 声明式函数,this绑定提升到全局
}, 1000)
},
}
o.loading() // 1秒后,控制台输出 1
o.loading() // 再1秒后,控制台输出 2
o.loading2() // 再1秒后,控制台输出 NaN 或其他
更一般的,在需要控制函数内 this
绑定指定区域时,采用箭头函数,一个例子如下:
const o = {
n: 1,
f() {
let fn = function () { console.log(++this.n); }; // 声明式函数,this绑定提升到全局
fn();
},
g() {
let gn = () => { console.log(++this.n); }; // 箭头函数,this绑定声明所在上下文
gn();
},
}
o.g() // 2
o.f() // NaN
不仅如此,箭头函数常出现在 promise
链中。或者说,箭头函数经常作为一个回调函数出现。
箭头函数没有自己的 arguments
对象。类似 this
的绑定,箭头函数会使用上下文 / 外部的 arguments
。下面一个例子中:
f
的参数隐式绑定;arguments[0]
为 f
的第一个参数 n
。function f(n) {
let g = () => arguments[0] + n; // f 的隐式参数绑定。arguments[0] 为 f 的第一个参数 n
return g();
}
f(3); // 3 + 3 = 6
JS 类没有默认的 arguments
字段,下面的例子中:
arguments
(如果存在);arguments
字段并进行访问(使用 this
指定)。globalThis.arguments = [1, 2, 3]
let f = () => { console.log(arguments[0]); }
class C {
constructor(a, ...r) { this.arguments = [a, ...r]; };
// arguments = [1, 2, 3];
g = () => { console.log(this.arguments[0]); };
}
f() // 1
(new C(10, 1, 2)).g() // 10
由于箭头函数的以上特性,在需要指定作用域的 this/arguments
时(setTimeout/Promise/闭包/装饰器/...
),能够起到简化的作用。下面一个装饰器的例子中:
this
和 arguments
访问,function sayHi(name) { console.log(`Hello, ${name}!`); }
function decorator1(fn, ms) {
return function () {
setTimeout(() => fn.apply(this, arguments), ms);
}
}
function decorator2(fn, ms) {
return function (...args) {
let nt = this;
setTimeout(function() { return f.apply(nt, args); }, ms);
}
}
let f = decorator1(sayHi, 1000);
let g = decorator2(sayHi, 1000);
f('Lily');
g('Jack');
在大多情况下,使用剩余参比使用 arguments
是更好的选择。
new
调用时会出错。const F = () => {
this.a = 10;
}
let f = new F() // 报错,不能作为构造器
new.target
。const G = (v) => {
if (!new.target) return new G(v); // 报错,不允许在箭头函数中出现 new.target
this.k = v;
}