为什么在使用 promises 时类方法内的'this'是未定义的?

129

我有一个 JavaScript 类,每个方法都返回一个 Q promise。我想知道为什么 method2method3 中的 this 是未定义的。是否有更正确的编写此代码的方法?

function MyClass(opts){
  this.options = opts;

  return this.method1()
    .then(this.method2)
    .then(this.method3);
}

MyClass.prototype.method1 = function(){
  // ...q stuff...

  console.log(this.options); // logs "opts" object

  return deferred.promise;
};

MyClass.prototype.method2 = function(method1resolve){
  // ...q stuff...

  console.log(this); // logs undefined

  return deferred.promise;
};

MyClass.prototype.method3 = function(method2resolve){
  // ...q stuff...

  console.log(this); // logs undefined

  return deferred.promise;
};

我可以使用 bind 来解决这个问题:

function MyClass(opts){
  this.options = opts;

  return this.method1()
    .then(this.method2.bind(this))
    .then(this.method3.bind(this));
}

但不是完全确定为什么需要bind; 是.then()this杀掉了吗?


当您使用bind()时,它会创建另一个函数,其作用域与您通过参数传递的作用域完全相同。虽然它只回答了您最后一个问题,但请查看Mozila的文档:https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Function/bind - Eduardo Henrique
3
为什么这个问题的链接被认为是 这个 的重复?我刚碰到了完全相同的问题,而且这种情况不能通过阅读那篇文章得到解答。虽然我已经看过那篇文章,但我正在学习关于 promises、ES6 类和 this 的知识。 - 4Z4T4R
7
@Paulpro,我不太确定这应该被标记为setTimeout问题的重复;因为这个问题出现在两种完全不同的方式中。希望解决promise上下文中的this作用域问题的人们会立即被引导到一个更间接的问题,其中被接受的答案使用了2009年的反模式。2 + 2 = 4 !== ((8+2)*6)/15 = 4 - SteamDev
3
IMO绝对不应该被标记为重复,特别是针对超时问题的提问。这个问题特别涉及到Promises,而答案对我来说非常有帮助。谢谢。 - Unknownweirdo
4个回答

183

this总是指方法被调用的对象。但是,当将该方法传递给then()时,您并没有调用它!该方法将存储在某个地方,并稍后从那里调用。如果要保留this,您需要这样做:

.then(() => this.method2())

或者如果必须使用ES6之前的方法,你需要在执行之前保留this

var that = this;
// ...
.then(function() { that.method2() })

10
优秀的答案 - 或者 ES6 之前的 ".then(this.method2.bind(this))"。 - Philip Murphy
9
我使用了.then(data => this.method(data)) - mateos
1
远远更简单的方法: .then((foo = this) => {... //然后使用foo代替this [也适用于ES6之前的版本]只要确保它没有被覆盖! - Hobbamok
低估了解释,设置承诺时遇到了一些困难,现在已经解决。谢谢。 - Remus.A

25

默认情况下,Promise处理程序在全局对象 (window) 的上下文中被调用。在严格模式下 (use strict;),上下文为 undefined。这就是发生在 method2method3 中的情况。

;(function(){
  'use strict'
  Promise.resolve('foo').then(function(){console.log(this)}); // undefined
}());

;(function(){
  Promise.resolve('foo').then(function(){console.log(this)}); // window
}());

对于method1,你使用this.method1()来调用它。这种调用方式将在this对象的上下文中调用它,该对象是你的实例。这就是为什么method1内部的上下文是该实例的原因。


2
现在这个答案真的帮助我理解了它。 - Volmarg Reiso

7
基本上,你在传递一个没有上下文引用的函数引用。这个this上下文是通过以下几种方式确定的:
  1. 隐式的。调用全局函数或没有绑定的函数会假定一个全局上下文。
  2. 通过直接引用。如果你调用myObj.f(),那么myObj将成为this上下文。
  3. 手动绑定。这是你的函数类,例如.bind.apply。这些你明确说明了this上下文是什么。这些始终优先于前两个。
在你的例子中,你正在传递一个函数引用,因此在它被调用时,它被暗示为一个全局函数或没有上下文的函数。使用.bind可以通过创建一个新函数来解决这个问题,其中this被显式设置。
*这只在非严格模式下成立,在严格模式下,this被设置为undefined
**假设你使用的函数没有被手动绑定。

1

单向函数获取它们的上下文 (this) 是从调用它们的对象中获得的 (这就是为什么 method1 有正确的上下文 - 它在 this 上被调用)。您正在将对函数本身的引用传递给 then。您可以想象,then 的实现类似于以下内容:

function then( callback ) {

  // assume 'value' is the recently-fulfilled promise value
  callback(value);
}

在这个例子中,callback是你的函数的引用。它没有任何上下文。正如你已经注意到的那样,在将其传递给 then 之前,可以通过绑定函数到上下文来解决这个问题。

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