使用类方法作为回调时的Promise.then执行上下文

时间:2017-01-18 17:52:05

标签: javascript ecmascript-6 promise es6-promise

为什么Promise.then在使用类方法作为回调时传递undefined的执行上下文,在使用"正常函数时传递window"?

类方法是否与其拥有的对象/类分离?为什么undefined而不是window

function normal() {
    console.log('normal function', this);
}
const arrow = () => {
    console.log('arrow function', this);
}

function strictFunction() {
    'use strict';
    console.log('strict function', this);
}

class Foo {
    test() {
        this.method(); // Foo
        Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo
        Promise.resolve().then(normal); // window
        Promise.resolve().then(arrow); // window
        Promise.resolve().then(strictFunction); // undefined
        Promise.resolve().then(this.method); // undefined <-- why?
    }

    method() {
        console.log('method', this);
    }
}

const F = new Foo();
F.test();

jsFiddle

我希望this.method的上下文丢失,但无法理解为什么this.method和&#34;正常&#34;之间的不同行为?和箭头功能。

这种行为有规格吗?我发现的唯一参考是Promises A +引用&#34;在严格模式下this内部将undefined;在草率模式下,它将是global object。&#34;

4 个答案:

答案 0 :(得分:7)

你在那里的引用告诉你原因:

  

在严格模式下this将在内部未定义;在草率模式下,它将成为全局对象。

ES6 spec说:

  

ClassDeclaration或ClassExpression的所有部分都是严格模式代码

因此,由于严格模式,未绑定的类方法中的this将为undefined

class A {
  method() {
    console.log(this);
  }
}

const a = new A();
a.method(); // A
const unboundMethod = a.method;
unboundMethod(); // undefined

如果您使用严格模式传递普通函数,则会产生相同的行为,因为this绑定在严格模式下默认为undefined,而不是设置为全局对象。

normalarrow thiswindow的原因是因为它们不在类中,因此不会以严格模式包装。

就promises和then方法而言,它只会将undefined作为this传递,但不会覆盖已绑定的this

如果你看一下PromiseReactionJob规范:

  

具有参数reaction和argument的作业PromiseReactionJob将适当的处理程序应用于传入值,并使用处理程序的返回值来解析或拒绝与该句柄关联的派生promise。

...
let handlerResult be Call(handler, undefined, «argument»).

Call的第二个参数是this值,设置为undefined

答案 1 :(得分:4)

这与Promises无关,而是调用this的上下文。

案例1:

this.method(); // Foo

此处methodFoo类中定义的函数,因此this被评估为触发函数的对象,thisthis.method中}。因此 - 显示Foo

案例2:

Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo

箭头函数是ES6的一个特性,其唯一属性是定义它的封闭上下文中的this上下文。该函数在this === Foo的上下文中调用,这就是显示的内容。

案例3:

Promise.resolve().then(normal); // window
Promise.resolve().then(arrow); // window

箭头函数将其上下文保留为窗口,因为它是一个箭头函数,并且在没有上下文的情况下评估正常函数,其中this在不在strict mode时被评估为窗口。

案例4:

Promise.resolve().then(strictFunction); // undefined

由于在窗口中声明的此函数体内请求strict modethis被评估为未定义。

案例5:

Promise.resolve().then(this.method); // undefined <-- why?

在这个spec中,定义所有类代码都是严格的代码:

  

ClassDeclaration或ClassExpression的所有部分都是严格模式代码。

答案 2 :(得分:1)

在我的案例中,它帮助定义了“自我”的简单解决方案。

<强> app.component.ts

export class AppComponent implements OnInit {
  public cards: Card[] = [];
  public events: any[] = [];

  constructor(private fbService: FacebookService) {
    this.fbService.loadSdk();
  }

  ngOnInit() {
    const self = this;

    this.fbService.getEvents().then((json: any) => {
      for (const event of json.data)
      {
        self.cards.push(
          new Card({
            imageUrl: 'assets/ny.jpg',
            id: event.id,
            name: event.name
          }),
        );
      }
    });
  }
}

<强> fb.service.ts

import { BehaviorSubject } from 'rxjs/Rx';
import { Injectable, NgZone } from '@angular/core';
import { Http } from '@angular/http';


declare var window: any;
declare var FB: any;

@Injectable()
export class FacebookService {
  events: any[];

  public ready = new BehaviorSubject<boolean>(false);

  constructor(private zone: NgZone) {
  }

  public loadSdk() {
    this.loadAsync(() => { });
  }


  public loadAsync(callback: () => void) {
    window.fbAsyncInit = () => this.zone.run(callback);
    // Load the Facebook SDK asynchronously
    const s = 'script';
    const id = 'facebook-jssdk';
    const fjs = document.getElementsByTagName(s)[0];
    // tslint:disable-next-line:curly
    if (document.getElementById(id)) return;

    const js = document.createElement(s);
    js.id = id;
    js.src = 'http://connect.facebook.net/en_US/all.js';
    fjs.parentNode.insertBefore(js, fjs);
  }

  public getEvents(): Promise<any> {
    return new Promise((resolve, reject) => {
      FB.init({
        appId: 'app_id',
        xfbml: true,
        status: true, 
        cookie: true,
        version: 'v2.10'
      });

      FB.api(
        '/salsaparty.bg/events',
        'GET',
        {
          access_token: 'acess_token'
        },
        function (response) {
          resolve(response);
        }
      );
    });
  }
}

答案 3 :(得分:0)

this.method未定义的原因是因为当你像这样使用它时,你实际上只是将没有上下文的函数作为回调。所以,当它运行时,它并不知道这一点。

如果您想维护上下文,请使用bind函数。

Promise.resolve().then(this.method.bind(this))

绑定将上下文绑定到方法。它基本上等同于:

Promise.resolve().then(((self) => () => self.method())(this))

是将上下文映射到范围中的变量的包装器。

使用类方法,当您将其作为变量获取时,它与包含对函数的引用的变量基本上没有区别。

例如:

const a = () => {};

class Foo {
    a() {}
}
const foo = new Foo();

console.log(a); // just a function
console.log(foo.a) // just a function
console.log(foo.a()) // a function called with a context of foo

当您在某个对象上调用方法时,例如foo.a()它与执行foo.a.call(foo)基本相同,您可以将a的上下文设置为foo。当您只需foo.a并将其与foo分开时,它与执行foo.a.call(window)(或节点中的global)相同。

以下是一些说明差异的代码。您还可以查看如果bind如何,它将保持上下文。

&#13;
&#13;
class Foo {
  constructor() {
    this.b = this.b.bind(this);
  }
  
  a () {
    return this;
  }
  
  b () {
    return this;
  }
}

const foo = new Foo();
const a = foo.a;
const b = foo.b;
const bound = foo.a.bind(foo);

console.log('A', foo.a().constructor.name);
console.log('A', a());
console.log('A', a.apply(foo).constructor.name);
console.log('A', bound().constructor.name);

console.log('B', foo.b().constructor.name);
console.log('B', b().constructor.name);
&#13;
&#13;
&#13;