前言
本篇文章主要是讲解我学习 JavaScript 中的 this 路上的疑惑部分和部分重要但不是很容易理解的知识点,所以需要带一点 this 的简单理解,主体 this 全面解析讲解可以转战 this
一、this
的本质解析
this
是函数执行时的动态上下文指针,其值完全由调用方式决定。它如同汽车的方向盘,控制权在驾驶员(调用者)手中,而非汽车(函数)本身。
javascript
const car = {
brand: 'Tesla',
start() {
console.log(`${this.brand} 启动`);
}
};
const bike = { brand: 'Giant' };
// 不同调用方式改变this指向
car.start(); // "Tesla 启动"
car.start.call(bike); // "Giant 启动"
ps:本文不细讲bind、call和apply,可以根据链接转架
二、调用位置与调用方式
1. 位置调用
我们先看看书上这一块
以下是其给的示例:
javascript
function baz() {
// 当前调用栈是:baz
// // 因此,当前调用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的调用位置
}
function bar() {
// 当前调用栈是 baz -> bar
// 因此,当前调用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的调用位置
}
function foo() {
// 当前调用栈是 baz -> bar -> foo
// 因此,当前调用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的调用位置
当时给我一顿骗啊,这里说 this 就和函数调用位置有关了,那么这里的 foo 中的 this 就该指向 bar,但是后面我又知道了 this 指向和函数调用方式有关。我们先理解完下面的方法调用再谈。
2. 方法调用
一、直接调用
先只谈一谈这个
javascript
var a = 1;
var obj = {
a: 2,
b: function () {
function fun() {
return this.a
}
console.log(fun());
}
}
obj.b();//1
其实这里本身不注意也会有点问题,我一开始是在Node.js 环境
中运行的。Node.js 环境:全局对象是 global
,var a = 1
不会 创建 global.a
。所以会输出undefined
,而不是之前我们所指的直接调用函数 this 指向全局的 1,解决方案:直接在浏览器运行即可。

好了,简单介绍完 this的直接调用就可回到我们刚刚要提出的问题:诶!根据书上的位置调用和直接调用方式,如果我们将一个函数 func 在另一个函数 testFunc 中直接调用呢?它的 this 是指向 testFunc 还是全局?不用想,文章开篇就说,完全由调用方式决定,结果就出来了。
javascript
function testFunc(){
let test = "test";
func();
}
function func(){
console.log(this.test);
}
testFunc(); //undefined
那书上为什么只写调用位置呢?其实只是为了方便理解,回到书上代码:
javascript
function baz() {
// 当前调用栈是:baz
// // 因此,当前调用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的调用位置
}
function bar() {
// 当前调用栈是 baz -> bar
// 因此,当前调用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的调用位置
}
function foo() {
// 当前调用栈是 baz -> bar -> foo
// 因此,当前调用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的调用位置
按照 foo 的调用方式,这里是直接调用,所以肯定是指向全局,但是它的调用栈确实是是 baz -> bar -> foo ,而我们需要用调用栈找到调用位置
,再根据绑定规则
(调用方式)来确定 this 的指向,这样解释我觉得你应该能更明白书上的意思。
二、作为对象的方法调用
依旧开车jym
javascript
var tools = ["油漆","锤子"]
const garage = {
tools: ["扳手", "千斤顶"],
listTools:function() {
return this.tools.join(', ');
}
};
console.log(garage.listTools()); //扳手, 千斤顶
这个很好理解,listTools函数作为obj的一个方法调用,这时候this指向调用它的对象,这里也就是obj。那么如果listTools函数不作为对象方法调用呢?看看下面这段代码:
javascript
var tools = ["油漆","锤子"]
const garage = {
tools: ["扳手", "千斤顶"],
listTools:function () {
return this.tools.join(',');
}
};
var test = garage.listTools
console.log(test()); //油漆,锤子 来咯
啊?!test 函数执行结果竟然是全局变量["油漆","锤子"]
。这就涉及Javascript的内存空间了,garage 对象的 listTools 属性存储的是对该匿名函数的一个引用。当赋值给 test 的时候,并没有单独开辟内存空间存储新的函数(诶,new 会啊,下面讲讲),而是类似让 test 存储一个指针,而这个指针指向 listTools 的匿名函数
。
最后两行代码可以这么理解:
javascript
var test = listTools // test指向listTools的匿名函数(相当于直接让test为这个函数)
console.log(test()); // 那这一步就好理解了,执行test就是直接执行该匿名函数
那我们再想想,直接在全局执行函数是什么?没错------直接调用,自然该函数执行结果指向全局!
3. 构造函数调用
javascript
function Engine(power) {
this.power = power;
this.start = function() {
console.log(`${this.power}马力引擎启动`);
};
}
const v6 = new Engine(300);
v6.start(); // "300马力引擎启动"
new
之后"自动挡"触发了:
- 创建(或者说构造了)一个全新的新对象
{}
- 这个新对象会被执行[[Prototype]]连接
- 这个新对象会被绑定到函数调用的
this
- 函数调用时自动返回这个新对象
new 好啊,最不容易混淆的就是 new 了。稍微提一嘴就是原型链,prototype对象的方法的this指向实例对象,因为实例对象的__proto__
已经指向了原型函数的prototype。这就涉及原型链的知识了,即方法会沿着对象的原型链进行查找。这里我虽然也写了文章原型链,但不是很细腻,可以去 MDN 查查或者看看大佬文章了解了解。
new
操作具有最高优先级,覆盖其他绑定方式:
javascript
function Car(model) {
this.model = model;
}
const boundCar = Car.bind({ model: "Toyota" });
const tesla = new boundCar("Model S");
console.log(tesla.model); // "Model S"(new覆盖bind)
三、箭头函数的特殊规则
箭头函数如同焊接固定的方向盘,this
在定义时永久绑定:
javascript
var tools = ["油漆","锤子"];
const garage = {
tools: ["扳手", "千斤顶"]
};
var fun = () => console.log(this.tools.join(','));
fun() //油漆,锤子
fun.call(garage) //油漆,锤子
箭头函数本身没有 this ,以其上下文的 this 作为自己的 this 值,也就是说箭头函数的 this 在词法层面就完成了绑定。apply,call方法只是传入参数,无法改变已绑定的 this 。
四、bind
的不可变性
bind
创建的函数副本也和箭头一样,如同焊死的方向盘:
javascript
const original = function() {
console.log(this.id);
};
const bound = original.bind({ id: 100 });
bound(); // 100
bound.call({ id: 200 }); // 仍输出100(绑定不可覆盖)
bound.apply(null); // 仍输出100
即使是 bind({ id: 100 }).bind({ id: 200 }) 也改变不了第一次的 bind ,倔的像生产队的驴。
五、小考点
箭头函数和 bind
前面说到,箭头函数"硬",bind绑定"稳",那来一场真男人之间的较量吧!
javascript
func = () => {
// 这里 this 指向取决于外层 this
console.log(this)
}
func.bind(1)() // Window
bind赢了指向就是1
,箭头赢了指向就是Window
,显然这场较量以 bind 失败结束了,其实还是比较容易理解,根据前文描述------箭头更"快"嘛。
bind 与 new
bind 不服啊,干不过箭头函数,我还干不过 new 吗?真让你干过了我前面夸 new 不白夸了,看看以下代码:
javascript
function func() {
console.log(this, this.__proto__ === func.prototype)
}
boundFunc = func.bind(1)
new boundFunc() // Object true
Function.prototype.bind
方法会创建一个新的函数 boundFunc
,并将 func 函数的 this 值绑定为传入的参数 1
。不过,当 bind 返回的函数使用 new
关键字调用时,bind 绑定的 this 值会被忽略,new
操作符会创建一个新的对象,并且这个新对象会作为 this 传递给构造函数。
六、非严格模式的安全转换
this 也有自己的脾气啊,当没人管的时候(非严格模式
),如果得出 this 指向是 undefined 或 null,那么 this 会自动指向全局对象
。
javascript
function checkThis() {
console.log(this === window);
}
checkThis.call(null); // true(转为全局对象)
checkThis.call(undefined); // true
checkThis.bind().bind(1)(); // true
checkThis.apply(); // true
- 浏览器环境转为
window
- Node.js 环境转为
global
- 严格模式下保持
null/undefined
终极决策树
遇到 this
疑惑时,按顺序检查:
- 函数是否被
new
调用? → 指向新实例 - 是否通过
call/apply/bind
调用? → 指向指定对象 - 是否作为对象方法调用? → 指向该对象
- 是否箭头函数? → 指向定义时外层 this
- 严格模式? →
undefined
: 全局对象
记住这个核心口诀:
"点前 new 绑 call apply,箭头定义定终身"
掌握这些规则,就能精准操控 JavaScript 的 this
机制,避免在代码中迷失方向。