在es6中引入了箭头函数或lambda。除了在最小语法上的优雅,最显著的函数差异是在箭头函数内的作用域
在正则函数表达式中,this关键字根据调用它的上下文绑定到不同的值。
在箭头函数中,这是词法绑定的,这意味着它从定义箭头函数的作用域(父作用域)关闭this,并且无论在哪里调用/调用它都不会改变。
箭头函数作为对象方法的限制
// this = global Window
let objA = {
id: 10,
name: "Simar",
print () { // same as print: function()
console.log(`[${this.id} -> ${this.name}]`);
}
}
objA.print(); // logs: [10 -> Simar]
objA = {
id: 10,
name: "Simar",
print: () => {
// Closes over this lexically (global Window)
console.log(`[${this.id} -> ${this.name}]`);
}
};
objA.print(); // logs: [undefined -> undefined]
在objA.print()的情况下,当使用常规函数定义print()方法时,它通过将此正确地解析为objA来进行方法调用,但当定义为arrow=>函数时失败。这是因为在常规函数中,当它作为对象(objA)的方法调用时,它就是对象本身。
然而,在箭头函数的情况下,它在词法上绑定到它定义的封闭作用域的this(在我们的例子中是global / Window),并在objA上作为方法调用时保持不变。
在对象的方法中,箭头函数比常规函数有优势,但只有当它在定义时被固定和绑定时才有优势。
/* this = global | Window (enclosing scope) */
let objB = {
id: 20,
name: "Paul",
print () { // Same as print: function()
setTimeout( function() {
// Invoked async, not bound to objB
console.log(`[${this.id} -> ${this.name}]`);
}, 1)
}
};
objB.print(); // Logs: [undefined -> undefined]'
objB = {
id: 20,
name: "Paul",
print () { // Same as print: function()
setTimeout( () => {
// Closes over bind to this from objB.print()
console.log(`[${this.id} -> ${this.name}]`);
}, 1)
}
};
objB.print(); // Logs: [20 -> Paul]
在objB.print()的情况下,其中print()方法被定义为调用console.log([${this. log)的函数。id} -> {this.name}])异步地作为setTimeout上的回调,当箭头函数被用作回调时,该函数正确地解析为objB,但当回调被定义为常规函数时失败。
这是因为传递给setTimeout(()=>..)的arrow =>函数在词法上关闭了它的父函数,即调用定义它的objB.print()。换句话说,arrow =>函数传递给setTimeout(()==>…因为objB.print()的调用就是objB本身。
我们可以很容易地使用function .prototype.bind()使定义为常规函数的回调工作,方法是将其绑定到正确的this。
const objB = {
id: 20,
name: "Singh",
print () { // The same as print: function()
setTimeout( (function() {
console.log(`[${this.id} -> ${this.name}]`);
}).bind(this), 1)
}
}
objB.print() // logs: [20 -> Singh]
然而,在异步回调的情况下,我们在函数定义时就知道了这个函数,它应该被绑定到这个函数,箭头函数就很方便,而且不太容易出错。
箭头函数的限制,需要在调用之间更改
在任何时候,我们都不能使用箭头函数,因为我们需要一个可以在调用时改变其值的函数。
/* this = global | Window (enclosing scope) */
function print() {
console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
id: 10,
name: "Simar",
print // The same as print: print
};
obj.print(); // Logs: [10 -> Simar]
const obj2 = {
id: 20,
name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // Logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]
以上都不能用于箭头函数const print = () => {console.log([${this. log])id} -> {this.name}]);}因为它不能被更改,并且将一直绑定到它所定义的外围作用域的this (global / Window)。
在所有这些例子中,我们用不同的对象(obj1和obj2)一个接一个地调用同一个函数,这两个对象都是在print()函数声明之后创建的。
这些都是虚构的例子,但让我们考虑一些更真实的例子。如果必须将reduce()方法编写成类似于处理数组的方法,那么同样不能将其定义为lambda,因为它需要从调用上下文(即调用它的数组)推断出这一点。
由于这个原因,构造函数永远不能定义为箭头函数,因为在声明构造函数时不能设置箭头函数。每次使用new关键字调用构造函数时,都会创建一个新对象,然后将其绑定到该特定调用。
此外,当框架或系统接受一个回调函数,并在以后使用动态上下文this调用时,我们不能使用箭头函数,因为这可能需要在每次调用时更改。这种情况通常出现在DOM事件处理程序中。
'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
// web-api invokes with this bound to current-target in DOM
this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
// TypeError; 'use strict' -> no global this
this.classList.toggle('on');
});
这也是为什么在像Angular 2+和Vue.js这样的框架中,模板组件绑定方法是常规的函数/方法,因为它们的调用是由绑定函数的框架管理的。(Angular使用Zone.js来管理视图模板绑定函数调用的异步上下文。)
另一方面,在React中,当我们想要传递一个组件的方法作为事件处理程序时,例如,<input onChange={this. this. this. this. this. this. this. this. this。handleOnchange} />,我们应该定义handleOnchange = (event)=> {this.props.onInputChange(event.target.value);}作为每次调用的箭头函数。我们希望这是为呈现的DOM元素生成JSX的组件的同一个实例。
本文也可以在我的Medium出版物中找到。如果你喜欢这篇文章,或者有任何评论和建议,请在Medium上鼓掌或留下评论。