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,可以根据链接转架
我们先看看书上这一块
以下是其给的示例:
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 指向和函数调用方式有关。我们先理解完下面的方法调用再谈。
先只谈一谈这个
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,解决方案:直接在浏览器运行即可。
好了,简单介绍完 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就是直接执行该匿名函数
那我们再想想,直接在全局执行函数是什么?没错——直接调用,自然该函数执行结果指向全局!
javascript
体验AI代码助手
代码解读
复制代码
function Engine(power) { this.power = power; this.start = function() { console.log(`${this.power}马力引擎启动`); }; } const v6 = new Engine(300); v6.start(); // "300马力引擎启动"
new 之后“自动挡”触发了:
new 好啊,最不容易混淆的就是 new 了。稍微提一嘴就是原型链,prototype对象的方法的this指向实例对象,因为实例对象的__proto__已经指向了原型函数的prototype。这就涉及原型链的知识了,即方法会沿着对象的原型链进行查找。这里我虽然也写了文章原型链,但不是很细腻,可以去 MDN 查查或者看看大佬文章了解了解。
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 创建的函数副本也和箭头一样,如同焊死的方向盘:
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绑定“稳”,那来一场真男人之间的较量吧!
javascript
体验AI代码助手
代码解读
复制代码
func = () => { // 这里 this 指向取决于外层 this console.log(this) } func.bind(1)() // Window
bind赢了指向就是1,箭头赢了指向就是Window,显然这场较量以 bind 失败结束了,其实还是比较容易理解,根据前文描述——箭头更“快”嘛。
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 疑惑时,按顺序检查:
记住这个核心口诀:
"点前 new 绑 call apply,箭头定义定终身"
掌握这些规则,就能精准操控 JavaScript 的 this 机制,避免在代码中迷失方向。