`this` | Quirks in JavaScript

/ 0评 / 0

this is not what you think it is.

考虑这样一则代码:

const A = function () {
  this.member = 'a';
  this.doSomething = () => {
    console.log(this.member);
  };
};

const B = function () {
  this.a = new A();
  this.doSomething = this.a.doSomething;
};

const b = new B();
b.doSomething(); // -> a

看起来很正常,或者表面上很正常,对吧。

但是再考虑这样一则代码:

const A = function () {
  this.member = 'a';
  this.doSomething = function () {
    console.log(this.member);
  };
};

const B = function () {
  this.a = new A();
  this.doSomething = this.a.doSomething;
};

const b = new B();
b.doSomething(); // -> undefined

为什么二者的行为不一样?


在 JS 中,this 的行为和其他「正常」的语言的行为都不一样。this 的值在调用时确定而不是根据声明时的位置确定。

一个简单的例子:

function foo() {
  this.a = 'bar';
}

let a = {};
a.foo = foo;

foo(); // `this` is global
a.foo(); // `this` is `a`
foo.apply(a); // `this` is `a`
foo.bind(a)(); // `this` is `a`

其实也不算非常奇怪,考虑到许多语言的面向对象支持也是类似 fun(ctx, ...args) 这样的形式。可以把 ctx.fun 当作 fun(ctx, 的语法糖。

如果仅限如此,倒是还行。不过比较难受的是回调函数的设计:

let obj = new (function () {
  this.member = 'a';
  setTimeout(function () {
    this.member = 'b';
  }, 1000);
})();

这样做并不会更改 obj.member 的值,因为第 4 行的 this 被绑定到 setTimeout 了。要正确地实现,必须借助一个中间变量:

let obj = new (function () {
  let instance = this;
  this.member = 'a';
  setTimeout(function () {
    instance.member = 'b';
  }, 1000);
})();

看起来不甚美观。所以,我们又有了箭头函数 () => {}.

箭头函数是一个典型的匿名函数。它的特性就是打破了上面所有 function 关于 this 的约定。简单而言,箭头函数的 this 绑定到更上一层的 this. 一个简单的例子:

const foo = () => {
  this.a = 'bar';
};

let a = {};
a.foo = foo;
foo(); // `this` is global
a.foo(); // `this` is still global
foo.bind(a)(); // `this` is still global
foo.apply(a); // `this` is STILL global

注意到第 8 行,此处的 this 不再是 a, 而是全局。而且 this 是在声明时就被绑定的,不能通过 bind, apply 或者 call 修改。

这样,如下代码就会正确绑定 this:

let obj = new (function () {
  this.member = 'a';
  setTimeout(() => {
    this.member = 'b';
  }, 1000);
})();

箭头函数所在的的上一层是 function () {...}, 从而 this 得以正确地从其继承。

但所有内嵌函数(为了兼容性或者其他原因)都使用 function (.) {...} 形式。这就意味着如下代码必须进行 .bind:

const C = function () {
  this.things = [1, 2, 3];
  this.forEach = this.things.forEach.bind(this.things);
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.