在匿名函数中预期使用 case `this`?

Intended use of case `this` in anonymous function?

问题

今天刚开始学习JS/ES7,想看看我是否理解this正确。

根据下面的两个代码片段,我想this的目的是可以选择是否要从父函数继承变量?

如果那是正确的,那么在第一个示例的 setInterval() 中是否存在使用 this 的合法用例?

var countup = {
    counter: 0,

    start:function(){
        var countup = this;
        setInterval(function(){
            countup.counter++;
        }, 1000);
    }
};

countup.start();

var countup = {
    counter: 0,

    start:function(){
        setInterval( () => {
            this.counter++;
        }, 1000);
    }
};

countup.start();

更新

另一种问我上述问题的方法是:

谁能想出 setInterval()this 的一个好的用例?

var countup = {
    counter: 0,

    start:function(){
        var countup = this;
        setInterval(function(){
            // insert good use case of `this` inside this function?
            countup.counter++;
        }, 1000);
    }
};

countup.start();

在第一个示例中,您在定义 startup 函数时分配 this 并将其分配给变量 countup 并且 this 此时存储在 countup 变量中,该变量将由 setInterval 函数中的回调形成的闭包引用。

如果您不这样做,那么在 function 中,this 将从该函数的执行上下文中获取。这意味着当 setInterval 将 运行 时,this 将是全局对象,因为传递给 setInterval 的回调在全局上下文中执行:

var countup = {
  counter: 0,

  start: function() {
    setInterval(function() {
      //this is not the lexical this, but the context of execution
      //since this.counter is not defined, this would print NaN (undefined + 1 == NaN)
      this.counter++;
      console.log("this == window::true ", this == window, "this.counter::NaN:: ", this.counter);
    }, 1000);
  }
};

countup.start();

在第二个例子中,提供给setInterval箭头函数() => {}总是从词法作用域中捕获this,所以this 总是绑定的。因此,无需手动从 lexical 范围捕获 this 即可正常工作。但请注意,两者都是匿名函数,即缺少 name 属性,此行为与函数的匿名性质无关。它们是这样定义的,一个是使用 function 语法定义的,另一个是箭头函数语法。

来自 Mozilla docs:

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. So while searching for this which is not present in current scope, an arrow function ends up finding the this from its enclosing scope.

setInterval 存在于全局 / WindowOrWorkerGlobalScope 上,因此 this 的值绑定到它。由于全局是唯一的并且在整个脚本中都可以访问,所以我认为 "bound" 函数和 setInterval

不会有任何用例

我认为 "inherit" 这个词不对。

每次调用函数时,都会出现 a hidden this argument("the this value",有时称为 "receiver")。 this 值取决于函数的调用方式。

this 被添加到语言中以启用类似于 Java 的面向对象语法。例如:

class Point {
  constructor(x, y) {
   this.x = x
   this.y = y
  }
  getX() {
    return this.x
  }
}

...或使用构造函数:

function Point(x, y) {
  this._x = x
  this._y = y
}

Point.prototype.getX = function() {
  return this._x
}

因此,this使程序员能够方便地将调用函数的对象称为方法:

const a = new Point(1, 2);
console.log(a.getX());

...但是,不幸的是,JavaScript 中的 this 有额外的复杂性。

复杂性源于以对象上的方法以外的方式调用函数的能力(有关完整详细信息,请参见 MDN)。例如:

function  myFunction() {
  console.log(this) // `undefined` in strict mode, `window` in non-strict
}
myFunction() // `myFunction` is not called as a method!

您的第一个示例提供了一个以不同于方法的方式调用函数的示例:在作为回调提供给 setInterval 的函数中,this 值应该是什么?碰巧的是,提供给 setInterval 的函数中 this 的默认值是全局对象(因此在本例中为 window)。

...这解释了我们在您的代码中看到的成语:将 this 的值分配给变量 that (我在您的示例中将其重命名为 "countup" ) 回调结束。这样我们就可以引用外部词法环境的 this 值,从 within 回调,每当它被调用时。

var countup = {
    counter: 0,
    start: function() {
        var that = this
        setInterval(function() {
            that.counter++
        }, 1000)
    }
}

你的第二个例子是做同样的事情,但在更现代的 JavaScript 中。箭头函数总是使用声明它们的词法环境的 this 值("lexical this")。所以我们可以去掉中间的 that 变量:

var countup = {
    counter: 0,
    start: function() {
        setInterval(() => this.counter++, 1000)
    }
}

setInterval 中,默认 this 值是全局对象,但是没有什么可以阻止您使用箭头函数或 call, apply or bind 来使用另一个 this 值。它只是对对象的引用(或 null,或 undefined)。因此,如果您想从 setInterval 更新共享状态,则可以通过 this 进行更新。这是否是一个好主意是主观的。我个人尽量避免使用 this,因为我认为这会使事情变得更复杂,但很多开发人员的想法恰恰相反。

根据我的经验,this 更倾向于在使用基于 class 的面向对象的代码库中使用,因为这些代码库的方法经常需要引用它们所针对的对象的状态被召唤了。

class Clock {
  start() {
    setInterval(() => this.tick(Date()), 1000) // using lexical `this`!
  }
  tick(time) {
    console.log(time)
  }
}
const c = new Clock
c.start()