引言:为什么要单独讨论JS中的 this?
JS 中的 this 之所以需要被单独拿出来讨论,是因为它在行为上与其他例如 Java、C++等 主流编程语言中的 this 有本质区别。
不妨来看个简单对照
下面是一段 C++ 实例对象的代码:
c++
class Person {
public: std::string name = "Alice";
void sayName() {
std::cout << this->name << std::endl;
}
};
int main() {
Person p;
std::string name = "Bob";
p.sayName();
return 0;
}
只要了解过this就必然知道这里最后的运行结果是
bash
Alice
但是如果是一个类似的 JavaScript 代码:
js
var bar = {
myName : 'Alice',
printName: function() {
console.log(myName);
}
}
myName = "Bob"
bar.printName();
最后得到的结果竟然是
bash
Bob
而这就是 JS 的 this 的特殊之处了,接下来不妨与我一同重新审视一下这位陌生的老朋友 this 了。
一、 JS 作者挖下的一个陷阱
早期 JavaScript 没有"类"
在 ES6 之前,JavaScript 并没有 class 关键字 ,也没有原生的面向对象语法。
所以开发者只能通过 构造函数 + this + prototype 的方法来实现类似 OOP 的效果,例如:
js
// 普通函数当 "构造函数" 使用
function Person(name) {
// 用 this 给实例绑定属性
this.name = name;
}
// 再使用 prototype 来共享方法
Person.prototype.sayHi = function() {
console.log("你好,我是" + this.name);
};
const alice = new Person("Alice");
alice.sayHi();
这里
this与其他语言this用法并没有差别
但问题在于,当我们将对象中的方法传到其他变量中时
js
const foo = alice.sayHi; // 合法的
foo();
最后运行的结果却是:

我们原本的期望是 alice.sayHi 是 alice 的方法,不管怎么传、怎么用,this 都应该指向 alice,但是在这里我们的this 居然"丢了"。
这就和 "this 应该指向所属对象" 的期望发生了冲突。
陷阱:this 的指向是由函数的 调用方式 决定的
例如:
js
var bar = {
myName: "XIXI",
printName: function() {
console.log(myName);
console.log(bar.myName);
console.log(this.myName);
console.log(this);
}
}
function foo() {
return bar.printName
}
let myName = 'Bob'
let _printName = foo();
// 访问对象调用
bar.printName();
// 普通函数调用
_printName();
在这里两次调用 printName() 函数看似是相同的,但是实际运行结果会产生偏差,不妨单独展示俩次调用的运行结果来对比一下。
- bar.printName() 访问对象调用

- _printName() 普通函数调用

不难发现,前两个输出都是 Bob 和 XIXI
- 运行
console.log(myName): 没有涉及this,这时myName就变成自由变量 ,沿着 作用域链(printName -> 全局作用域)查找到了全局作用域中的let myName = Bob - 运行
console.log(bar.myName): 指定了this指向bar,此时myName也就顺理成章的找到了bar中的myName: "XIXI"
但是后两者差别就很大了
- 运行
console.log(this.myName):通过后续console.log(this)能看到,在访问对象中this是指向bar对象,但是在普通函数调用中this的指向却是window。
这里就是 JS 中
this的漏洞:
- 调用对象内的函数,this 就指向在对象中
- let 申明的变量,不会挂载在全局window对象上
不好的地方:
- var 申明的变量,会挂载在全局window对象上
- 当函数没有调用对象,就是普通函数,this 就指向全局对象(window)
根据上述的差异,我们再来重新审视一下调用结果
- 访问对象调用:
this->bar,所以this.myName也就是XIXI - 普通函数调用:
this->window,所以window.myName就是Bob,对吗?别忘了let申明是不会挂载在window上的,所以最后结果应当是undefined
导致的后果:
根据上文我们知道,在两种情况下会有数据会与 window 产生牵连,也就是 var申明变量 和 普通函数调用
如果在代码中大量使用 var 申明,也就难免会产生 var 申明的变量名 会与 普通函数名 冲突
js
function bar() {}
var bar = 123;
这个时候调用 bar就会导致后面的bar = 123将前面的bar() {}覆盖了。
所以多次使用var会导致很多冲突且难管理(全局变量的污染),将window污染了
解决陷阱?
1. 使用严格模式('use strict')
javascript
'use strict';
function foo() {
this.x = 1;
}
2. 使用箭头函数(无自己的 this)
kotlin
class Counter {
constructor() {
this.count = 0;
}
// 箭头函数继承外层 this
handleClick = () => {
this.count++;
}
}
二、背景知识 ------ JS 的执行模型与作用域机制
代码执行的三个阶段
编译阶段 -> 创建执行上下文 -> 执行阶段
| 阶段 | 主要作用 | 与 this 的关系 |
|---|---|---|
| 编译阶段 | 词法分析、作用域收集、变量/函数提升 | this 不参与 |
| 创建执行上下文 | 构建变量环境和词法环境 | this 未绑定 |
| 执行阶段 | 逐行执行语句、函数调用、表达式求值 | this 根据调用方式动态绑定 |
词法作用域 与 动态 this 对比
-
自由变量查找:沿着作用域链向上查找(编译时确定)
-
this查找:取决于函数如何被调用(运行时决定)
对比示例:
js
const obj = {
name: 'XIXI',
myName() {
console.log(this.name); // this 动态绑定
function foo() {
console.log(name); // name 是自由变量,走词法作用域
}
}
};
三、this 的五种绑定规则(由强到弱)
1. 显式绑定:call / apply / bind
优先级最高,由开发者主动指定 this 值
js
let bar = {
myName: "极客邦",
test1: 1
}
function foo() {
this.myName = "极客时间";
}
// .call() 或 .apply() 或 .bind() 将 this 指定为 bar
foo.call(bar)
foo.apply(bar)
foo.bind(bar)
2. new 绑定
使用 new 构造函数时,this 指向新创建的实例对象
js
function CreateObj() {
// new CreateObj()内部使用步骤:
// 1. var temObj = {}
// 2. CreateObj.call(temObj) 将 this 强制指向 temObj
// 3. temObj.__proto__ = CreateObj.prototype 将新对象的原型链,连接到构造函数的 prototype 上
// 4. return temObj 返还创建的新对象
console.log(this)
this.name = "极客时间"
}
// 如果这里没有使用 new,那么 this 就会默认指向全局定义域中的 window
var myObj = new CreateObj();
3. 隐式绑定(对象方法调用)
函数作为对象属性被调用 → this 指向该对象
js
const obj = {
name: "XIXI",
sayName: function() {
console.log(this.name); // this === obj
}
};
obj.sayName(); // this 指向 obj
-
陷阱 :方法赋值给变量后丢失绑定
jsconst obj = { name: 'XIXI', say() { console.log(this.name); } }; obj.say(); // XIXI const f = obj.say; f(); // undefined(严格模式下为 TypeError)
4. 默认绑定(普通函数调用)
-
非严格模式 :
this指向全局对象(浏览器中为window) -
严格模式('use strict') :
this为undefined
5. 箭头函数
箭头函数不绑定 this ,而是继承外层词法作用域的 this
js
const obj = {
name: 'Bob',
foo() {
setTimeout(() => {
console.log(this.name); // "Bob"
}, 100);
}
};
绑定优先级总结 :
new>call/apply/bind> 对象方法 > 普通函数