this是Javascript语言的一个关键字。随着使用场合的不同,this的值会发生变化。
但是有一个总的原则,那就是this始终指的是,调用函数的那个对象。

在全局作用域下

在浏览器环境下:全局作用域下,this 指向 Window 对象.

1
2
3
4
console.log(this);
// Window { .. }
this === window;
// true

在 node 环境下:全局作用域下,this 指向 global 对象。

1
2
3
4
console.log(this);
// global
this === global;
// true

严格模式,在 node 环境下:遵循严格模式的规范,this 不再指向全局对象。

1
2
3
'use strict';
console.log(this);
// {}

函数对象作用域下

1
2
3
4
5
function foo() {
console.log(this);
}
foo();
// global / Window

严格模式,在 node 环境下:

1
2
3
4
5
6
'use strict';
function foo() {
console.log(this);
}
foo();
// undefined

作为对象方法的调用

作为对象方法时,this 指向该对象。

1
2
3
4
5
6
7
8
let obj = {
foo: function() {
console.log(this);
}
};
obj.foo();
// { foo: [Function] }
// obj 的值实际上是个匿名类的对象,foo 的值实际上是个匿名函数

注意到:在函数体内使用的、在函数体外定义(声明)的变量,是 传引用 的。

1
2
3
4
5
6
7
8
9
10
11
12
function func() {
console.log(this);
}
let obj = {
foo: func
};
obj.foo();
// { foo: [Function func] }
let foo1 = obj.foo;
foo1();
// global

在回调函数里面会遇到一些坑

1
2
3
4
5
6
7
var obj = {
foo2: function() {
console.log(this);
setTimeout(this.foo, 1000);
}
}
obj.foo2();

执行这段代码我们会发现两次打印出来的 this 是不一样的:
第一次是 foo2 中直接打印 this,这里指向 obj 这个对象,
但是在 setTimeout 中执行的 this.foo ,却指向了全局对象.
this.foo 当作一个参数传给 setTimeout 这个函数,就像它需要一个 fun 参数,在传入参数的时候,其实做了个这样的操作 fun = this.foo,这里我们直接把 fun 指向 this.foo 的引用;执行的时候其实是执行了 fun() 所以已经和 obj 无关了,它是被当作普通函数直接调用的,因此 this 指向全局对象。

解决:
为了解决这个问题,我们可以利用 闭包 的特性来处理:

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
name: 'qiutc',
foo2: function() {
console.log(this);
var _this = this;
setTimeout(function() {
console.log(this); // Window
console.log(_this); // Object {name: "qiutc"}
}, 1000);
}
}
obj.foo2();

可以看到直接用 this 仍然是 Window;因为 foo2 中的 this 是指向 obj,我们可以先用一个变量 _this 来储存,然后在回调函数中使用 _this,就可以指向当前的这个对象了;

setTimeout 的另一个坑
如果直接执行回调函数而没有绑定作用域,那么它的 this 是指向全局对象(window),在严格模式下会指向 undefined,然而在 setTimeout 中的回调函数在严格模式下却表现出不同:

1
2
3
4
5
6
'use strict';
function foo() {
console.log(this);
}
setTimeout(foo, 1);
// window

按理说我们加了严格模式,foo 调用也没有指定 this,应该是出来 undefined,但是这里仍然出现了全局对象,难道是严格模式失效了吗?

并不,即使在严格模式下,setTimeout 方法在调用传入函数的时候,如果这个函数没有指定了的 this,那么它会做一个隐式的操作—-自动地注入全局上下文,等同于调用 foo.apply(window) 而非 foo()

当然,如果我们在传入函数的时候已经指定 this,那么就不会被注入全局对象,比如: setTimeout(foo.bind(obj), 1);

箭头函数

在 ES6 的新规范中,加入了箭头函数,它和普通函数最不一样的一点就是 this 的指向了,
上文我们使用闭包来解决 this 的指向问题,但如果用上了箭头函数就可以更完美的解决了:

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
name: 'qiutc',
foo: function() {
console.log(this);
},
foo2: function() {
console.log(this);
setTimeout(() => {
console.log(this); // Object {name: "qiutc"}
}, 1000);
}
}
obj.foo2();

可以看到,在 setTimeout 执行的函数中,本应该打印出在 Window,但是在这里 this 却指向了 obj,原因就在于,给 setTimeout 传入的函数(参数)是一个箭头函数:

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
关键点就是,箭头函数内的 this 执行定义时所在的对象,就是指向定义这个箭头函数时作用域内的 this,也就是 obj.foo2 中的 this,即 obj;所以在执行箭头函数的时候,它的 this -> obj.foo2 中的 this -> obj

简单来说, 箭头函数中的 this 只和定义它时候的作用域的 this 有关,而与在哪里以及如何调用它无关,同时它的 this 指向是不可改变的

call / apply / bind

js 中的函数对象,其 prototype 中定义了如下三个函数:

1
2
3
4
5
6
func.call(thisArg[, arg1[, arg2[, ...]]]);
// 执行函数 func,使用第一个参数作为 this,其他参数作为 func 的实参,一一对应。
func.apply(thisArg[, [arg1, arg2, ...]]);
// 执行函数 func,使用第一个参数作为 this,第二个参数为数组,数组中的每个元素作为 func 的实参,一一对应。
var foo = func.bind(thisArg[, arg1[, arg2[, ...]]]);
// 绑定 func 的 this 和所有参数,返回一个新的函数,但不执行它。

bind 的 this 对 new 关键字无效,但其他实参有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
function A(name) {
console.log(this.name);
this.name = name;
console.log(this.name);
}
var obj = {
name: "obj"
};
var B = A.bind(obj, "B");
var b = new B('b');
// undefined B
console.log(obj.name);
// obj

要注意,=> 语法下的 this 不受影响,该语法下 this 视为 const 变量,不接受修改。