js的this指向
前言
js的this指向一直是个老生常谈的问题,我们知道,.前面是谁,this就是谁;非严格模式下,this有时也会指向window(浏览器端)或者global(nodejs端);严格模式下有时会是undefined;箭头函数没有this,它的this取决于外部包裹它的函数。
本章我们就通过一些例子来探究一下js的this指向问题,不过,在正式开始前,我们先来思考一个问题?
this为什么会被设计出来?它的用途是干什么的?
this的由来
对象和方法
js中可以使用{}来声明对象,对象里的属性可以描述它的特征,对象里的方法可以用来描绘对象的一些行为动作,比如:
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
console.log("Hello!");
};
user.sayHi(); // Hello!上述代码就描绘了一个user对象,他的名字叫John,他的年龄是30岁。他有个sayHi的方法,所以他可以向别人打招呼,然后他调用了自己打招呼的方法,跟我们说了声Hello!。
🚩面向对象编程
其实,上面这种用对象描述实体的方式,就是所谓的 面向对象编程,简称为 “OOP”。
OOP 是一门大学问,本身就是一门有趣的科学。怎样选择合适的实体?如何组织它们之间的交互?这就是架构,有很多关于这方面的书,例如 E. Gamma、R. Helm、R. Johnson 和 J. Vissides 所著的《设计模式:可复用面向对象软件的基础》,G. Booch 所著的《面向对象分析与设计》等。
参考:https://zh.javascript.info/object-methods#jian-tou-han-shu-mei-you-zi-ji-de-this
上述的示例代码其实还可以简写一下:
let user = {
name: "John",
age: 30,
sayHi: function() {
console.log("Hello");
}
};
// 方法简写看起来更好,对吧?
let user = {
name: "John",
age: 30,
sayHi() { // 与 "sayHi: function()" 一样
console.log("Hello");
},
};方法中的this
通常,对象方法需要访问对象中存储的信息才能完成其工作。比如上面例子里的John向别人打招呼时,需要顺便介绍一下自己的名字和年龄,这时就需要用到 user 的 name和age属性。所以,为了能访问到自己,就诞生出了this关键字。
this 的值就是在点之前的这个对象,即调用该方法的对象。
let user = {
name: "John",
age: 30,
sayHi() {
// "this" 指的是“当前的对象”
console.log(this.name);
}
};
user.sayHi(); // John在这里 user.sayHi() 执行过程中,this 的值是 user。
技术上讲,也可以在不使用 this 的情况下,通过外部变量名来引用它:
let user = {
name: "John",
age: 30,
sayHi() {
console.log(user.name); // "user" 替代 "this"
}
};
user.sayHi(); // John🤫……但上面的代码是不可靠的。如果我们决定将
user复制给另一个变量,例如admin = user,并赋另外的值给user,那么它将访问到错误的对象。比如:
let user = {
name: "John",
age: 30,
sayHi() {
console.log(user.name); // 导致错误
}
};
let admin = user;
user = null; // 重写让其更明显
admin.sayHi(); // TypeError: Cannot read property 'name' of null我们可以画图来理解一下上面的例子,看一下为什么会报错:

如图所示,JS的变量存储在栈内存中,对象和方法存储在堆内存中,user实际存储的是对象的引用,一串地址空间
- 首先声明了一个
user变量,将其指向堆内存的一个对象上。 - 然后又声明了一个
admin变量,也保存刚才的地址,所以此时的admin和user指向的是同一块内存空间,即图中的对象保存的地方。 - 这时做了一个操作,将
user变量置为空,那么此时的user变量就找不到原来的对象了。 - 而
admin保存的仍旧是原先的那块地址空间,所以还能找到对象里的sayHi方法,但是由于user = null,所以打印输出时会报错,TypeError: Cannot read property 'name' of null。
所以这时就体现出
this的好处了,将代码中的user替换成this,代码就能正常运行了。如下所示:
let user = {
name: "John",
age: 30,
sayHi() {
console.log(this.name);
}
};
let admin = user;
user = null;
admin.sayHi(); // John继续画图分析一下:

前两步和之前的一样,我们直接从第三步开始看
- 将变量
user置为空后,变量user就找不到之前的对象了,就没什么用了。 - 此时的变量
admin还保存着之前对象的引用地址,所以指向的还是刚才声明的对象,所以能找到sayHi方法,在打印输出中,我们改为了this.name,此时的this就是当前对象。因为使用的admin.sayHi()调用的该方法,所以此时的this就是admin(.之前的对象就是this),,所以当调用this.name时,正常输出了John。
this指向的不确定性
在方法里使用
this时,并不受限制,可以随便用,JS并不会直接将this绑定在当前方法上,具体的指向只有在调用函数时,根据上下文才会被确定,也就是说,this的指向是不确定的。
指向window、global或者undefined
// "use strict"
function sayHi() {
console.log(this);
}
sayHi()如上面的例子,直接使用function声明一个方法,方法里面打印this,而我们并不把这个方法绑定到某个对象上,而是直接使用方法名调用,这时有两种情况
- 非严格模式下:
浏览器端,
this指向全局对象window
nodejs端,
this指向全局对象global
- 严格模式下(在头部增加
"use strict"),此时this为undefined
指向.前面的对象
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
console.log(this.name);
}
// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;
// 这两个调用有不同的 this 值
// 函数内部的 "this" 是“点符号前面”的那个对象
user.f(); // John(this === user)
admin.f(); // Admin(this === admin)
admin['f'](); // Admin(使用点符号或方括号语法来访问这个方法,都没有关系。)如上面的例子,this就是.前面的对象。我们画图来分析一下:

- 我们先声明了一个变量
user,指向了堆内存中的一个对象上。(实际保存的是地址的引用) - 又声明了一个变量
admin,也指向了堆内存中的一个对象上,2中的对象与1中的对象不是同一个,分别保存在堆内存的不同位置上。(实际保存的是地址的引用) - 使用
function声明了一个sayHi方法,这时又在堆内存开辟了一块新的内存空间,保存这个方法,sayHi变量保存该方法的引用。 user.f = sayHi,为user对象添加一个变量f指向sayHi方法。admin.f = sayHi,为admin对象添加一个变量f指向sayHi方法。user.f(),因为为user增加的f变量指向了sayHi方法,所以此时的f就是sayHi方法,此时sayHi中的this就是user对象本身,所以打印出了John。admin.f(),因为为admin增加的f变量指向了sayHi方法,所以此时的f就是sayHi方法,此时sayHi中的this就是admin对象本身,所以打印出了Admin。
🚩解除
this绑定的后果如果你经常使用其他的编程语言,那么你可能已经习惯了“绑定
this”的概念,即在对象中定义的方法总是又指向该对象的this。在 JavaScript 中,
this是“自由”的,它的值是在调用时计算出来的,它的值并不取决于方法声明的位置,而是取决于在“点符号前”的是什么对象。在运行时对
this求值的这个概念既有优点也有缺点。一方面,函数可以被重用于不同的对象。另一方面,更大的灵活性造成了更大的出错的可能。这里我们的立场并不是要评判编程语言的这个设计是好是坏。而是要了解怎样使用它,如何趋利避害。
参考:https://zh.javascript.info/object-methods#jian-tou-han-shu-mei-you-zi-ji-de-this
箭头函数没有自己的this
我们知道,箭头函数没有自己的this,如果访问 this,则会从外部包裹它的函数中获取。我们看下面这个例子,这里 forEach 中使用了箭头函数,所以其中的 this.title 其实和外部方法 showList 的完全一样。那就是:group.title,所以最终会打印正确的结果。
"use strict"
let group = {
title: "Our Group",
students: ["John", "Pete", "Alice"],
showList() {
this.students.forEach((item) => {
console.log(this.title + ': ' + item)
});
}
};
group.showList();但是当我们换成普通的匿名函数时,就会报错:
"use strict"
let group = {
title: "Our Group",
students: ["John", "Pete", "Alice"],
showList() {
this.students.forEach(function (item) {
// TypeError: Cannot read property 'title' of undefined
console.log(this.title + ': ' + item)
});
}
};
group.showList();报错是因为 forEach 运行它里面的这个函数,但是这个函数的 this 为默认值 this=undefined(严格模式下默认为undifined),因此就出现了尝试访问 undefined.title 的情况。
总结
在方法里使用
this时,并不会受限制,可以随便用,js并不会直接将this绑定在当前方法上,具体的指向只有在调用函数时,根据上下文才会被确定。直接使用
function声明一个方法时,通过方法名直接调用,此时的this指向分为两种情况:2.1 非严格模式下,浏览器端
this指向window,nodejs端指向global2.2 严格模式下,
this === undefined使用
object.fn()【对象.方法】的形式调用时,this就是.前面的对象。箭头函数默认没有
this,如果访问this,则会从外部包裹它的函数中获取。