class 方法中的 This 与箭头函数

This in class method vs arrow function

美好的一天

我最近 运行 遇到了一个 st运行ge 情况,我在 class 的成员函数中的 this 值未定义。我知道有很多与未定义的 this 上下文相关的问题,但我找不到对这个问题的任何解释。我很想知道为什么会这样。在这个例子中,为什么 setModified 函数的箭头函数实现保留了它的 this 上下文,而 class 上的函数却没有。

这个例子在块 class

上的 setModified 函数中中断
class point {
    constructor(x, y) {
        this._x = x || 0;
        this._changeEvents = [];
    }

    set changeEvent(eventFunction) {
        this._changeEvents.push(eventFunction);
    }

    set x(value) {
        this._x = value;
        this.runChangeEvent();
    }

    set y(value) {
        this._y = value;
        this.runChangeEvent();
    }

    get x() {
        return this._x;
    }

    get y() {
        return this._y;
    }

    runChangeEvent() {
        this._changeEvents.forEach(event => event(this));
    }
}

class renderItem {
    constructor(canvas) {
        this._canvas = canvas;
    }

    render(){

    }
}

class block extends renderItem {
     constructor(canvas) {
        super(canvas);
        this._modified = true;
        this._topLeft = new point(0, 0);
        this._topLeft.changeEvent = this.setModified;
    }

    //Using a method on the class as a callback, it breaks
    setModified(){
         this._modified = true;//breaks, this is undefined
         console.log(this);
     }

    //Sets
    set topLeft(value) { this._topLeft = value; }

    //Gets
    get topLeft() { return this._topLeft }
}
//Creates an instance of the block
const bl = new block(null);
bl.topLeft.x = 20;

但是当你将 setModified 函数更改为箭头函数时(也在 class 上),它起作用了:

class point {
    constructor(x, y) {
        this._x = x || 0;
        this._y = y || 0;
        this._changeEvents = [];
    }

    set changeEvent(eventFunction) {
        this._changeEvents.push(eventFunction);
    }

    set x(value) {
        this._x = value;
        this.runChangeEvent();
    }

    set y(value) {
        this._y = value;
        this.runChangeEvent();
    }

    get x() {
        return this._x;
    }

    get y() {
        return this._y;
    }

    runChangeEvent() {
        this._changeEvents.forEach(event => event(this));
    }
}

class renderItem {
    constructor(canvas) {
        this._canvas = canvas;
    }

    render(){

    }
}

class block extends renderItem {
     constructor(canvas) {
        super(canvas);
        this._modified = true;
        //Using an arrow function on the class instance as a callback, it works
        this.setModified = () => {
            this._modified = true;//works
            console.log(this);
        };
        this._topLeft = new point(0, 0);
        this._topLeft.changeEvent = this.setModified;
    }

    //Sets
    set topLeft(value) { this._topLeft = value; }

    //Gets
    get topLeft() { return this._topLeft }
}
const bl = new block(null);
bl.topLeft.x = 20;

为什么 class 上的成员函数会丢失 this 上下文而不是箭头函数?

An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords.

箭头函数的范围与常规函数没有什么不同,但是正如您在上面从 MDN 中看到的,它们处理 this 不同的绑定。

箭头函数不会创建自己对 this 的绑定,实际上它是从封闭范围(即 class)继承的。在class中,存在this._modified

An arrow function does not have its own this. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules.

this 是在执行上下文中确定的,正如我们所展示的,箭头函数是有效的,因为 this 与封闭范围的 this 保持一致。但是使用正常功能...

In strict mode, however, if the value of this is not set when entering an execution context, it remains as undefined

如果您在函数错误之前 console.log(this),您会看到它是 undefined。但是为什么是undefined呢?阅读隐式丢失部分下的 here。但基本上默认绑定是在将其作为普通的、未修饰的函数引用调用时应用的,它来自赋值。在严格模式下,this 将 return undefined 而不是在全局范围内。

在您的情况下不会发生隐式绑定,this 可能会隐式丢失。

One of the most common frustrations that this binding creates is when an implicitly bound function loses that binding, which usually means it falls back to the default binding, of either the global object or undefined, depending on strict mode.

如果您将 runChangeEvent() 中的调用更改为 event.call(this),您将看到 this 实际上是 point 的调用(不是您想要的)。如果您调用 this._topLeft.changeEvent = this.setModified.bind(this);,您将拥有您想要的范围。

Calling f.bind(someObject) creates a new function with the same body and scope as f, but where this occurs in the original function, in the new function it is permanently bound to the first argument of bind, regardless of how the function is being used.

查看 link 了解更多信息,我还推荐前面提到的这个 online book