JavaScript中的 this 关键字

前言

this 是 JavaScript 中的一个关键字,它指向一个对象。在不同场合,this 有不同含义,在 严格模式非严格模式 之间它也有一些区别。

那么如何判断 this 关键字的指向呢?首先需要记住的一点是,this 所指的对象取决于它使用的时候所处的环境,而不是它定义的时候所处的环境(箭头函数除外)。下面分几种情况来分别讨论。

全局上下文中的 this 值

在全局上下文环境中,this 总是指代 全局对象 ,无论是否在严格模式下。

1
2
3
4
5
6
console.log(this.document === document); // true
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37

浏览器中的全局对象通常是 window 对象。

函数上下文中的 this 值

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

直接调用函数

如果函数是被直接调用的,那么函数内部的 this 值在非严格模式下指向 全局对象 ,在严格模式下 this 将保持它进入执行环境时的值,如果 this 未被执行的上下文环境定义,那么它会是 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
//非严格模式
function f1(){
return this;
}
f1() === window; // true
//严格模式
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true

对象方法中的 this

当以对象里的方法的方式调用函数时,它们的 this 指向调用该函数的对象。

1
2
3
4
5
6
7
8
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37

注意,在何处或者如何定义函数完全不影响 this 的行为,this 值是在函数调用时而不是定义时绑定。看下面这个例子:

1
2
3
4
5
6
7
8
9
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37

我们首先定义一个函数,然后再将其添加到o.f。这样做 this 值也指向了 o 对象。

类似的,this 的绑定只受最靠近的成员引用的影响。在下面的这个例子中,我们把一个方法 g 当作对象 o.b 的函数调用。在这次执行期间,函数中的 this 将指向 o.b。

1
2
3
4
5
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // 42

原型链中的 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

这个例子中,对象 p 没有属于自己的 f 属性,它的 f 属性在它的原型对象上。但是在调用 p.f() 时,f() 的 this 值仍然指向了 p 对象。

getter 与 setter 中的 this

当一个函数作为 getter 或 setter 调用时,再次,它们都会绑定 this 到设置属性或得到属性的那个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function modulus(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase(){
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(o, 'modulus', {
get: modulus, enumerable:true, configurable:true});
console.log(o.phase, o.modulus); // -0.78 1.4142

构造函数中的 this

当一个函数被作为一个构造函数来使用时,它的 this 会与被它创建的新实例对象绑定。

1
2
3
4
5
6
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // 37

闭包中的 this

在闭包内层函数里的 this,由于闭包的函数没有直接调用对象,它会在非严格模式下指向全局对象 global/window,严格模式下为 undefined。

1
2
3
4
5
6
7
8
9
10
11
var num = 1;
var obj = {
num: 2,
getNum: function() {
return (function() {
return this.num;
})();
}
};
console.log(obj.getNum()); // 1

事件处理函数中的 this

内联事件处理函数中的 this

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

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

上面的 alert 会显示 button。但注意只有外层代码中的 this 是这样设置的。看下面的例子:

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

在这种情况下,没有设置内部函数的 this,所以它指向 global/window 对象。

DOM 事件处理函数中的 this

当一个函数被用作事件处理函数时,它的 this 指向触发该事件的元素。

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);
}

显式绑定 this 值

以上所讲的都是 this 值的默认绑定与隐式绑定,但 JavaScript 提供的绝大多数函数以及我们自
己创建的所有函数都可以使用 call()apply() 以及 ES5 引入的 bind() 方法来显式绑定函数的 this 指向。

call() 和 apply() 方法

当一个函数的函数体中使用了 this 关键字时,通过所有函数都从 Function 对象的原型中继承的 call() 方法和 apply() 方法调用时,它的值可以绑定到一个指定的对象上。

1
2
3
4
5
6
7
8
9
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

bind() 方法

ECMAScript 5 引入了 Function.prototype.bind。调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的。

1
2
3
4
5
6
7
8
9
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty

ES6 箭头函数的 this 值

ECMAScript 6 引入了 箭头函数=>)的概念。

箭头函数内部没有自己的 this 值,它的 this 是来自它的包含环境上下文的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//非箭头函数
function Person() {
// 构造函数 Person() 定义的 `this` 就是新实例对象自己
this.age = 0;
setInterval(function growUp() {
// 在非严格模式下,growUp() 函数定义了其内部的 `this`
// 为全局对象, 不同于构造函数Person()的定义的 `this`
this.age++;
}, 1000);
}
var p = new Person();
//箭头函数
//它会捕获其所在上下文的 this 值,作为自己的 this 值
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向了 person 对象
}, 1000);
}
var p = new Person();

由于箭头函数的 this 已经在词法层面完成了绑定,所以通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this 并没有什么影响:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
var f = v => v + this.base;
var b = {
base : 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3)

总结

简单做一下总结,在判断一个 this 值的指向时,有以下几个原则:

  • 原则一: this 指向的是直接调用函数的那个对象,没有的话就指向全局对象 global/window(非严格模式)。
  • 原则二: this 指向的对象就是它使用的时候(不是定义的时候)所处的环境的上下文。
  • 原则三: 当 new 关键字出现时,this 指代 new 出来的那个对象。
------ 本文结束 ------
坚持原创技术分享,您的支持将鼓励我继续创作!