超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

JS 中的 this

JS 中的 this 是一个很容易让人迷糊的概念,本文主要总结了各种情况下的 this 指向。

全局环境中的 this

全局环境中的 this 总是指向全局对象 window 上的。

this.a1 = 10;
var a2 = 20;

console.log(a1); // 10
console.log(this.a2); // 20

非箭头函数中的this

需要记住一点:在一个函数上下文中,函数中的 this 始终是由函数的调用者提供,若不存在调用者,则非严格模式下 this 指向全局顶级对象(浏览器中为 window 对象),而严格模式下 this 为 undefined。接下来看几个例子:

var person = {
name: "Jack",
greet: function() {
console.log("Hello, " + this.name);
}
};
person.greet(); // "Hello, Jack"

很明显,函数的调用者是 person,所以 this 指向 person。

看起来很简单,再看个例子试试:

var name = 'Global window'
var personJack = {
name: "Jack",
greet: function() {
console.log("Hello, " + this.name);
}
};
var greet = personJack.greet;
var personMack = {
name: "Mack",
greet: greet
}
greet(); // 输出 "Hello, Global window"
personMack.greet();// 输出 "Hello, Jack"

// 输出
//
"Hello, Global window"
// greet 的调用者为 personJack,所以 this.name 指向 personJack.name
"Hello, Jack"

注意,判断函数中 this 的指向,是在执行的时候决定的,而语句 greet() 在执行时,greet 函数并没有调用者,所以,this 指向全局对象 window,所以输出 window.name

而语句 personMack.greet() 在执行时,greet 的调用者为 personJack,所以 this.name 指向 personJack.name

再看另一个例子:

var name = 'Global';
var person = {
name: 'Person',
full: {
name: 'Person Full',
getName: function() {
return this.name;
}
},
};
console.log(person.full.getName()); // Person Full

很明显,这次 getName 的调用者是 person.full ,this 指向它。

严格模式(use strict)下的 this

在上一节中提到,如果函数的调用者不存在,函数中的 this 则指向全局变量 window,这样无疑在有些情况下会出现难以检查的错误:

var name = 'global';
var obj = {
name: 'object',
getName() {
console.log(this.name);
}
}

var getName = obj.getName();
getName(); // global

在上面的例子中,由于 window 对象恰好存在一个 name 属性,所以也不会报错。但是,如果代码规格比较大,定位错误无疑是非常困难的。

使用严格模式,如果函数不存在调用者,this 将会指向 undefined 而不再是全局对象 window 了:

'use strict'
var name = 'global';
var obj = {
name: 'object',
getName() {
console.log(this.name);
}
}

var getName = obj.getName;
getName(); // Uncaught TypeError: Cannot read property 'name' of undefined

此时会抛出错误,很容易定位到错误的位置。

关于严格模式的更多细节,请查看 MDN

箭头函数中的this

箭头函数中的 this 指向其在 声明时所在的最近的一个非箭头函数this 。并且箭头函数的 this 指向在箭头函数声明后是不会且不能改变的。

function a() {
return () => {
return () => {
console.log(this)
}
}
}
a()()(); // window

在上面的例子中,最内层箭头函数声明时,最近的非箭头函数是 f(a) 而 f(a) 在执行时没有调用者,this 指向全局变量 window,所以箭头函数的 this 也指向全局变量 window。

如果改变 f(a) 中 this 的指向呢:

var obj = {
b: b,
}

obj.a()()(); // obj
a.bind(window)()()(); // Window
a.bind(obj)()()(); // obj

前面提到“箭头函数的 this 指向在箭头函数声明后是不会且不能改变”,这和上面的例子并不冲突。也就是说箭头函数的指向在声明阶段就已经确定了,比如在上面的例子,箭头函数中的 this 在声明的时候就确定指向了 f(a) 中的 this,但是 f(a) 中的 this 指向的目标可能是在执行阶段才确定的,再看个例子:

function b() {
var self = this;
return () => {
return () => {
console.log(this)
console.log(this === self)
}
}
}

var obj = {
b: b,
}

obj.b()()(); // obj true
b.bind(window)()()(); // Window true
b.bind(obj)()()(); // obj true

从上面的例子可以看出,箭头函数中的 this 始终指向 f(a) 中的 this。

参数为函数时的 this

这种情况看起来很绕,实际上掌握了上面几点后就很简单,看下面的例子:

var obj = {
data: [1, 2, 3],
getDouble: function() {
return this.data.map(function(item) {
console.log(this);
return item * 2
});
},
getTreble: function() {
return this.data.map(item => {
console.log(this);
return item * 3;
});
}
}

obj.getDouble(); // log window
obj.getTreble(); // log obj

在调用 obj.getEven() 时,需要了解 map 内部是如何执行的,下面是 map 的一个 polyfill:

if (Array.prototype.map === undefined) {
Array.prototype.map = function(fn) {
var rv = [];
for(var i=0, l=this.length; i<l; i++)
rv.push(fn(this[i]));
return rv;
};
}

从代码中可以看出,函数在调用时并没有调用者,因此 this 指向 window。而 getTreble 中参数为箭头函数,this 指向和其调用方式没有关系,都指向 obj。

bind、call、apply

这三个函数都是用于改变函数中 this 的指向,具体概念及接口不再说明,这里只举一个例子:

var obj = {
print() {
console.log(this);
}
}
var a = obj.print;
var b = obj.print.bind(obj);
a(); // window
b(); // obj

还有一个场景,如果想要我们的匿名函数能够访问到指定对象,就可以使用 bind,看下面的例子:

var a = 10;
var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}.bind(this), 0)
},
getB: function() {
setTimeout(function() {
console.log(this.a)
}, 1000)
}
}

obj.getA() // 10
obj.getB() // 20