css
主页 > 网页 > css >

JS中 this 的疑难杂症介绍

2025-05-31 | 佚名 | 点击:

一、this 的本质解析

this 是函数执行时的动态上下文指针,其值完全由调用方式决定。它如同汽车的方向盘,控制权在驾驶员(调用者)手中,而非汽车(函数)本身。


 

javascript

体验AI代码助手

代码解读

复制代码

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. 位置调用

我们先看看书上这一块

image.png 以下是其给的示例:


 

javascript

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

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,解决方案:直接在浏览器运行即可。

image.png

好了,简单介绍完 this的直接调用就可回到我们刚刚要提出的问题:诶!根据书上的位置调用和直接调用方式,如果我们将一个函数 func 在另一个函数 testFunc 中直接调用呢?它的 this 是指向 testFunc 还是全局?不用想,文章开篇就说,完全由调用方式决定,结果就出来了。


 

javascript

体验AI代码助手

代码解读

复制代码

function testFunc(){ let test = "test"; func(); } function func(){ console.log(this.test); } testFunc(); //undefined

那书上为什么只写调用位置呢?其实只是为了方便理解,回到书上代码:


 

javascript

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

var tools = ["油漆","锤子"] const garage = { tools: ["扳手", "千斤顶"], listTools:function() { return this.tools.join(', '); } }; console.log(garage.listTools()); //扳手, 千斤顶

这个很好理解,listTools函数作为obj的一个方法调用,这时候this指向调用它的对象,这里也就是obj。那么如果listTools函数不作为对象方法调用呢?看看下面这段代码:


 

javascript

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

var test = listTools // test指向listTools的匿名函数(相当于直接让test为这个函数) console.log(test()); // 那这一步就好理解了,执行test就是直接执行该匿名函数

那我们再想想,直接在全局执行函数是什么?没错——直接调用,自然该函数执行结果指向全局!

3. 构造函数调用


 

javascript

体验AI代码助手

代码解读

复制代码

function Engine(power) { this.power = power; this.start = function() { console.log(`${this.power}马力引擎启动`); }; } const v6 = new Engine(300); v6.start(); // "300马力引擎启动"

new 之后“自动挡”触发了:

  1. 创建(或者说构造了)一个全新的新对象 {}
  2. 这个新对象会被执行[[Prototype]]连接
  3. 这个新对象会被绑定到函数调用的 this
  4. 函数调用时自动返回这个新对象

new 好啊,最不容易混淆的就是 new 了。稍微提一嘴就是原型链,prototype对象的方法的this指向实例对象,因为实例对象的__proto__已经指向了原型函数的prototype。这就涉及原型链的知识了,即方法会沿着对象的原型链进行查找。这里我虽然也写了文章原型链,但不是很细腻,可以去 MDN 查查或者看看大佬文章了解了解。

fd2cafe5a4cca3c0f347ca0cbc18d00.jpg

new 操作具有最高优先级,覆盖其他绑定方式:


 

javascript

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

func = () => { // 这里 this 指向取决于外层 this console.log(this) } func.bind(1)() // Window

bind赢了指向就是1,箭头赢了指向就是Window,显然这场较量以 bind 失败结束了,其实还是比较容易理解,根据前文描述——箭头更“快”嘛。

bind 与 new

bind 不服啊,干不过箭头函数,我还干不过 new 吗?真让你干过了我前面夸 new 不白夸了,看看以下代码:


 

javascript

体验AI代码助手

代码解读

复制代码

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

体验AI代码助手

代码解读

复制代码

function checkThis() { console.log(this === window); } checkThis.call(null); // true(转为全局对象) checkThis.call(undefined); // true checkThis.bind().bind(1)(); // true checkThis.apply(); // true

终极决策树

遇到 this 疑惑时,按顺序检查:

  1. 函数是否被 new 调用? → 指向新实例
  2. 是否通过 call/apply/bind 调用? → 指向指定对象
  3. 是否作为对象方法调用? → 指向该对象
  4. 是否箭头函数? → 指向定义时外层 this
  5. 严格模式? → undefined : 全局对象

记住这个核心口诀:

"点前 new 绑 call apply,箭头定义定终身"

掌握这些规则,就能精准操控 JavaScript 的 this 机制,避免在代码中迷失方向。

原文链接:https://juejin.cn/post/7510058875384954906
相关文章
最新更新