JavaScript 中的 this

多数情况下函数的调用方式决定了 this 的值(运行时绑定)。

ES5 使用 bind 方法设置 this 值,使得不用考虑函数如何被调用。

全局上下文

在全局执行上下文下,无论是否是严格模式,this 都指向全局对象。

1
2
3
4
5
6
7
8
9
// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b)

函数上下文

作为函数调用

在函数内部,this 的值取决于函数被调用的方式。

  • 调用时不设置 this

    • 非严格模式: window

      1
      2
      3
      4
      5
      function f1() {
      return this;
      }

      console.log( f1() );// window 对象
    • 严格模式: undefined

      1
      2
      3
      4
      5
      6
      function f2() {
      "use strict";
      return this;
      }

      console.log( f2() );// undefined

作为方法调用

类上下文

  • 类函数中的 this 表示类
  • 实例函数中的 this 表示类的原型
  • 派生类调用 super() 后才有 this,相当于 this = new Base(),Base 为基类

构造函数

构造函数中的 this 为构造函数的返回值,默认返回值为创建的实例对象,但返回值可以被修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function C(){
this.a = 37;
}

var o = new C();
console.log(o.a); // logs 37


function C2(){
this.a = 37;
return {a:38};
}

o = new C2();
console.log(o.a); // logs 38

类中的 this

可以通过 bind 将 this 指向实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Car {
constructor() {
// Bind sayBye but not sayHi to show the difference
this.sayBye = this.sayBye.bind(this);
}
sayHi() {
console.log(`Hello from ${this.name}`);
}
sayBye() {
console.log(`Bye from ${this.name}`);
}
get name() {
return 'Car';
}
}

class Bird {
get name() {
return 'Bird';
}
}

class CarChild extends Car {
get name() {
return 'CarChild';
}
}


let car1 = new Car();
let bird1 = new Bird();

// this 的值依赖于调用者
car1.sayHi(); // Hello from Car

bird1.sayHi = car1.sayHi;
bird1.sayHi(); // Hello from Bird


bird1.sayBye = car1.sayBye;//绑定了 this 的方法, 函数借用中的 'this' 不依赖与调用者
bird1.sayBye(); // Bye from Car



let test1 = new CarChild();
test1.sayHi();// Hello from CarChild
test1.sayBye();// Bye from CarChild

原型链中的 this

当被当做方法调用时,this 被设置为调用该函数的对象。

1
2
3
4
5
6
7
8
9
10
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5

绑定函数上下文

  • call

  • apply

  • bind

    • 使用 bind 创建的新函数,新函数中的 this 永久绑定到第一个参数,无论新函数什么时候被调用,this 都是使用 bind 时的第一个参数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function f(){
      return this.a;
      }

      //新建的函数 g 中, this 值将永久被绑定到第一个参数 {a:"azerty"}
      var g = f.bind({a:"azerty"});
      console.log(g()); // azerty

      var h = g.bind({a:'yoo'}); // bind 只生效一次!
      console.log(h()); // azerty

      var o = {a:37, f:f, g:g, h:h};
      console.log(o.a, o.f(), o.g(), o.h()); // 37, 37, azerty, azerty

DOM 事件处理函数

使用 addEventListener 添加的事件处理函数, this 指向触发事件的元素,即 currentTarget。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 被调用时,将关联的元素变成蓝色
function bluify(e){
console.log(this === e.currentTarget); // 总是 true

// 当 currentTarget 和 target 是同一个对象时为 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}

// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');

// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}

内联事件处理函数

当代码被内联 on-event 处理函数 调用时,它的this指向监听器所在的DOM元素:

1
2
3
<button onclick="alert(this.tagName.toLowerCase());">
Show this// button
</button>

下面示例没有设置内部函数的 this,所以它指向 global/window 对象(因为非严格模式下调用的函数未设置 this 时指向的默认对象)

1
2
3
<button onclick="alert((function(){return this})());">
Show inner this// window
</button>

箭头函数

箭头函数中的 this 表示创建时的作用域,而不是运行时的。通过 call apply bind 给箭头函数绑定 this 时,this 会被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// 尝试使用call来设定this
console.log(foo.call(obj) === globalObject); // true

// 尝试使用bind来设定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true