箭头函数和this

3

我正在浏览 Twitter,发现了这条推文:

https://twitter.com/_ericelliott/status/855598297939144704

这是推文中的代码:

const double = () => this.x * 2;
const numDouble = double.bind({ x: 5 });
numDouble();

当您在控制台中运行此片段时,它将生成NaN。为什么?作者明确地绑定了x的值,但仍然显示NaN。
作者还指定箭头函数无法绑定this。我知道箭头函数从周围作用域词法上绑定this的值。那么作者为什么这样说?
请澄清我的疑惑,感谢您提前的帮助。

x => x + this.y 大多数情况下是 function (x) { return x + this.y }.bind(this) 的语法糖。 - zloctb
@zloctb:在那个“大多数”中还隐藏着一些其他的差异。它们没有prototype属性和相关对象,它们没有自己的arguments绑定,它们没有自己的super绑定(如果相关的话)...... - T.J. Crowder
2个回答

5

箭头函数不会绑定this。根据MDN:

没有绑定的this

在箭头函数出现之前,每个新函数都定义了自己的this值 (在构造函数的情况下是一个新对象,在严格模式下是未定义的 函数调用,如果函数作为“对象方法”被调用,则为上下文对象等)。这证明在面向对象的编程风格中非常烦人。

因此,在您的示例中,this将是全局对象window,显然没有名为x的属性。

示例:

function foo() {
  let arrow = () => {
    console.log(this);     // will use foo's this as arrow will never have its own this
  }
  
  arrow.call({"x": "x"});  // ... even if we specify it using bind, call, or apply
}

foo.call({"y": "y"});      // specifying the this for foo (this value will eventually be used by arrow because it will be availbale in its scope)


你能再澄清一下吗? - Kishan Patel
根据MDN的说法,箭头函数不绑定this,也没有它们自己的this。 - Kishan Patel
@KishanPatel 箭头函数就像浅层函数一样。它们没有自己的 this(即使使用 bind 也不会有)。它们只是使用它们所在作用域中可用的任何 this。我将发布一个示例。 - ibrahim mahrir
哦,这就是为什么在我们的情况下,“this”会回退到全局对象。而且,在全局窗口上,“this”是未定义的,如果您尝试将其乘以2,则会显示NaN。 感谢澄清我的疑惑。 - Kishan Patel
@KishanPatel 没错!而且'this'未定义。我想你是指'x'未定义。请检查上面的例子! - ibrahim mahrir

2
记住的关键是:
箭头函数与函数闭合变量的方式相同,可以关闭“this”。实际上,这是相同的机制。在创建箭头函数的位置,无论“this”是什么,在调用该箭头函数时,“this”都将是什么。“this”永远不会是其他任何东西。箭头函数忽略了它们被调用时的“this”。
如果您记得这一点,您将再也不会困惑于箭头函数中的“this”了。
当您在控制台中运行此片段时,它将产生NaN。如何?作者明确地绑定了x的值,但仍然显示NaN。 numDouble = double.bind({ x: 5 }) 创建一个新函数(numDouble),当调用它时,它将使用提供为bind第一个参数的值({ x: 5 })调用原始函数(double)。但由于箭头函数忽略了它们调用时的“this”,因此bind无法控制它们使用的“this”。
作者还指定箭头函数无法绑定“this”。正如我所知道的那样,箭头函数从周围作用域词法地绑定“this”的值。
没错,这意味着您无法更改它。词法绑定是闭包的工作方式。这个箭头函数:
const a = () => {
    console.log(typeof this);
};

它正好像传统函数对 thisWhereFunctionWasCreated 所采取的方式一样处理 this

最初的回答:

const thisWhereFunctionWasCreated = this;
const t = function() {
    console.log(typeof thisWhereFunctionWasCreated);
};

就像在调用函数时无法更改thisWhereFunctionWasCreated变量使用的内容一样,当您调用时也无法更改this a使用的内容。 (如果thisWhereFunctionWasCreated不是一个const,则可以更改它所持有的,但不能更改哪个thisWhereFunctionWasCreated变量使用。 但是,在该示例中,它是常量,因为this是常量。)
由于箭头函数完全忽略了调用它时使用的this,因此使用任何机制尝试告诉箭头函数使用哪个this都是无效的。无论您是通过将函数作为方法隐式指定thisobj.arrow()),还是通过callapplyarrow.call(obj))指定this,还是通过bindconst boundArrow = arrow.bind(obj); boundArrow();)指定this,它仍然会使用它关闭的this

"use strict";

function Ctor() {
    
    // `this` will be the object created by `new Ctor`; grab it
    this.name = "outerThis";
    const outerThis = this;
    
    // `traditional` doesn't close over `this`, so you CAN change
    // what `this` it uses when you call it, in various ways
    function traditional(testNum) {
        console.log(testNum, "traditional:", getName(this));
    }
    
    // `arrow` closes over `this`, so you CAN'T change
    // what `this` it uses when you call it
    const arrow = testNum => {
        console.log(testNum, "arrow:      ", getName(this));
    };

    // Remember that the `this` in a direct call is the global
    // object in loose mode, `undefined` in strict mode; this
    // code is in strict mode
    console.log("Direct call (default `this`):");
    traditional(1);              // 1 traditional: window
    arrow(1);                    // 1 arrow:       outerThis
    
    console.log("`obj.xyz()`:");
    const obj = {
        name: "obj",
        arrow,
        traditional
    };
    obj.traditional(2);          // 2 traditional: obj
    obj.arrow(2);                // 2 arrow:       outerThis

    console.log("Using `call`:");
    traditional.call(obj, 3);    // 3 traditional: obj
    arrow.call(obj, 3);          // 3 arrow:       outerThis

    console.log("Using `bind` and calling result:");
    const boundTraditional = traditional.bind(obj);
    const boundArrow = arrow.bind(obj);
    boundTraditional(4);         // 4 traditional: obj
    boundArrow(4);               // 4 arrow:       outerThis
}

function getName(t) {
    switch (t) {
        case undefined:
            return "undefined";
        case window:
            return "window";
        default:
            return t.name;
    }
}

new Ctor();
.as-console-wrapper {
    max-height: 100% !important;
}

<最初的回答> 当在箭头函数上调用bind时,唯一可以做的就是将参数绑定到它上面。

const arrow = (x, y) => x + y;
console.log(arrow(2, 3));      // 5

const arrowWith2 = arrow.bind(null, 2);
console.log(arrowWith2(3));    // 5

const arrowWith2And3 = arrow.bind(null, 2, 3);
console.log(arrowWith2And3()); // 5

这还将结果函数的名称设置为"bound x"(其中x是原始函数的名称。因此,上面的arrowWith2.name"bound arrow")。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接