原文链接:
www.codementor.io/dariogarcia…
这是理解Javascript中的"This"的第二篇文章([译] 理解Javascript中的"This"),可以对箭头函数有进一步的理解。
之前在使用箭头函数的时候也犯过文中的错误,那时候还不清楚this的指向还有箭头函数并没有属于自己的this这一点。
这篇文章可以作为“理解JavaScript中的"This"”的第二篇文章。
我们将会通过同样的例子,但是我们将会使用箭头函数代替普通函数来对比输出结果。
关于第二篇文章的动机是:尽管箭头函数是ES6中新增的强大功能,但是一定不能滥用它们。
“this”默认的上下文
箭头函数不会绑定它们自己的this,相反,它们会从父级作用域中继承,被称为“词法作用域(静态作用域)”。这让箭头函数成为了一些场景中的最佳选择,但同时有些场景却是非常糟糕。
译者注:
lexical scoping:词法作用域/静态作用域
既函数的作用域在函数定义的时候就决定好了。
与其相反的是动态作用域:函数的作用域在函数调用的时候才决定。
来看一个例子:
var value = 1; function foo() { console.log(value); } function bar() { var value = 2; foo(); } bar(); // 结果是 ???假设JavaScript采用静态作用域,分析过程如下:
执行foo函数,首先从foo函数内部查找是否有变量value;
如果没有,就根据书写的位置查找上一层的代码,我们发现value等于1,所以结果打印为1。
假设JavaScript采用动态作用域,分析过程如下:
执行foo函数,依然是从foo函数内部查找是否有局部变量value。
如果没有,就从调用函数的作用域,也就是bar函数内部查找value变量,所以结果打印为2。
JavaScript采用的是静态作用域,所以这个例子的结果是1。
我们看看第一个例子改写为箭头函数后:
// define a function
const myFunction = () => {
console.log(this);
};
// call it
myFunction();
我们可以期待this是什么呢?...没错,这和普通函数一样,window/全局对象。同样的结果但并不是同样的原因。在普通函数下,作用域默认绑定为全局作用域;但像我前面说的,箭头函数并没有属于它们自己的this,但它们继承于父级作用域,在这个例子中,它就是全局作用域。
如果我们使用严格模式(use strict)会怎样呢?并没有什么不一样的,会是一样的结果,因为作用域来自父级。
箭头函数作为方法
const myObject = {
myMethod: () => {
console.log(this);
}
};
现在这样呢?
在这个例子中,有的人会说,这取决于方法被谁调用,和普通函数一样。但事实并不是这样的,让我们一起看看...
myObject.myMethod() // this === window or global object
const myMethod = myObject.myMethod;
myMethod() // this === window or global object
很奇怪,对吧?请记住:箭头函数并不会绑定它们自己的作用域,而是继承于父级。而在这个例子中就是window/全局对象。
让我们改变列子一点点。
const myObject = {
myArrowFunction: null,
myMethod: function () {
this.myArrowFunction = () => { console.log(this) };
}
};
我们需要调用myObject.myMethod()去初始化myObject.myArrowFunction,然后让我们看看输出结果是怎么样的。
myObject.myMethod() // this === myObject
myObject.myArrowFunction() // this === myObject
const myArrowFunction = myObject.myArrowFunction;
myArrowFunction() // this === myObject
现在清楚了吗?当我们调用myObject.myMethod(),我们使用myMethod内部的箭头函数初始化myObject.myArrowFunction,所以它将继承其作用域。我们可以清楚的看到一个完美的应用例子:闭包。
显式、Hard 和 New 绑定
如果我们尝试通过这些手段绑定作用域会发生什么呢?
让我们看看...
const myMethod = () => {
console.log(this);
};
const myObject = {};
显式绑定
myMethod.call(myObject, args1, args2, ...) // this === window or global object
myMethod.apply(myObject, [array of args]) // this === window or global object
Hard绑定
const myMethodBound = myMethod.bind(myObject);
myMethodBound(); // this === window or global object
New绑定
new myMethod(); // Uncaught TypeError: myMethod is not a constructor
正如你看到的,不管我们尝试用哪种方式绑定作用域都不会生效。同样,箭头函数没有构造函数,所以你不能使用new。
API回调
这一部分非常的有趣。对于接口回调(异步代码)而言,箭头函数是一个非常好的选择,仅当我们使用闭包时,让我们看看下面的代码...
myObject = {
myMethod: function () {
helperObject.doSomethingAsync('superCool', () => {
console.log(this); // this === myObject
});
},
};
这是一个完美的例子,我们需要在异步过程中做一些事情,我们等待一些结果去执行一些操作,然而我们不需要担心正在操作的作用域。
但是如果我们需要提取和重构一些代码用以复用,这又会发生什么呢?例如:
const reusabledCallback = () => {
console.log(this); // this === window or global object
};
myObject = {
myMethod: function () {
helperObject.doSomethingAsync('superCool', reusabledCallback);
},
};
如果我们这样做了,我们会破环当前工作的代码,还有请记住:不管我们尝试怎么绑定作用域都将会失效。所以如果你决定这样做,你必须使用普通函数并手动绑定作用域,例如:
const reusabledCallback = function () {
console.log(this);
};
myObject = {
myMethod: function () {
helperObject.doSomethingAsync('superCool', reusabledCallback.bind(myObject));
},
};
结论
箭头函数是ES6中新增的强大功能,但当我们使用的时候必须保持谨慎和明智的判断。我不断的寻找那些不适用箭头函数的地方,它会导致一些错误难以追踪,尤其是当我们并不理解它的原理时。
我的建议是,在闭包或者回调的情况下,箭头函数是一个很好的选择。但在class/object、构造函数的方法中,它并不是一个好的选择。
PS:
箭头函数还有很多有趣的特点,例如:arguments或者prototype,但这篇文章的主题是this的作用域。更多信息可以查看这个文档**mozilla web docs**。
如果你有任何问题或意见,请给我留言!