this 的绑定魔法:五大规则 + V8 原理 + 实战大乱斗!

------小Dora 的 JavaScript 修炼日记 · Day 3

"你永远叫不醒一个被 this 背叛的开发者,除非他亲手写了一遍 bind。"

------来自闭包协会的匿名投稿


🐒 一、为什么你总是搞不清 this?

因为 JS 的 this 绑定机制像极了感情世界:

  • 有时候你以为它属于你,其实它根本不认识你(默认绑定)
  • 有时候你以为它是你朋友介绍的,结果中途它偷偷换了个对象(隐式丢失)
  • 有时候你亲自把它牵过来,但别人还是把它抢走了(bind 被 new 抢)
  • 有时候你亲手创造了它,它却说 "你不是我爹,我 new 出来的自己是主角"(new 绑定)
  • 有时候你觉得终于稳了,它却说:我不变,天塌了我也不动(箭头函数)

so... 是时候系统性搞懂 this 了!


🧬 二、this 是怎么在 V8 引擎里绑定的?

在函数执行时,JS 引擎会进入一个新的执行上下文。

这个上下文中有个 ThisBinding,会根据调用方式 决定 this 的值。

V8 引擎里这套流程叫做 call record 创建 & this binding 初始化

简单粗暴记住:

javascript 复制代码
function foo() {
  console.log(this);
}
  • 调用方式是 foo():没有主人,this 就是 global
  • 调用方式是 obj.foo():谁点的我,我就认谁当爹

🧠 这不是函数的错,是你调用方式的问题!------V8 内心独白


⚔️ 三、五种绑定规则,像极了感情修罗场

1. 🧍‍♂️ 默认绑定:没人管我,那我就自由(其实很孤独)

scss 复制代码
function show() {
  console.log(this); // 浏览器里是 window,严格模式下是 undefined
}
show();

口诀:没人领我,我就是 window


2. 🧑‍🤝‍🧑 隐式绑定:谁喊我我跟谁

javascript 复制代码
const obj = {
  name: "Dora",
  say() {
    console.log(this.name);
  },
};
obj.say(); // Dora

但是注意这个"叛逃行为":

ini 复制代码
const fn = obj.say;
fn(); // undefined

口诀:被谁喊是关键,脱了壳(对象),就打回默认绑定!


3. 📞 显式绑定:call / apply / bind 一声令下 this 改嫁

scss 复制代码
function sayHi() {
  console.log(this.name);
}
const person = { name: "前端之神" };

sayHi.call(person);    // 前端之神
sayHi.apply(person);   // 前端之神
sayHi.bind(person)();  // 前端之神

但是注意,bind 是真心绑定,一旦绑定,不改二心:

rust 复制代码
const fn = sayHi.bind(person);
fn.call({ name: "黑客" }); // 还是 前端之神

口诀:call / apply 约你一次,bind 是要结婚


4. 👶 new 绑定:this 绑定到新出生的孩子身上

ini 复制代码
function Person(name) {
  this.name = name;
}
const p = new Person("小Dora");
console.log(p.name); // 小Dora

new 做了什么:

  1. 创建空对象
  2. 把 this 指到这个对象
  3. 执行函数
  4. 返回对象

注意:哪怕你 bind 了,也会被 new 干掉!

ini 复制代码
function Foo() {
  this.name = "原始";
}
const f = new Foo.bind({ name: "绑错了" })();
console.log(f.name); // 原始

口诀:bind 是配偶,new 是亲生的,新对象最大


5. 🧨 箭头函数:this?不好意思,我爸是谁我出生就定了

javascript 复制代码
const obj = {
  name: "外部对象",
  say: () => {
    console.log(this.name);
  },
};
obj.say(); // undefined

再来看这个经典闭包场景:

javascript 复制代码
function outer() {
  const arrow = () => {
    console.log(this.name);
  };
  arrow();
}
outer.call({ name: "V8执行器" }); // V8执行器

箭头函数不会自己设置 this,只会用定义时 外层的 this,写死了,改不了!

口诀:箭头是亲爹定的,后天你喊破喉咙都没用!


📚 四、最全 this 优先级口诀(面试一招 KO)

bash 复制代码
new > bind > call/apply > 隐式 > 默认

搞笑记忆法:

"new 是亲爹,谁都改不了;

bind 是真爱,锁死你;

call/apply 是约会,喊你一次就散;

隐式是同事,不靠谱;

默认是孤儿,没人管。"


💣 五、搞一把:this 全家桶大乱斗测试

scss 复制代码
globalThis.name = "全局";

function show() {
  console.log(this.name);
}
const obj = {
  name: "对象",
  show,
};

const arrow = () => {
  console.log(this.name);
};

obj.show();            // 对象(隐式)
show();                // 全局(默认)
show.call(obj);        // 对象(显式)
new show();            // undefined(新 this)
arrow();               // 全局(箭头不变)

🔧 六、手写 bind:三行搞定,顺便封印 this

javascript 复制代码
Function.prototype.myBind = function (context, ...args) {
  const fn = this;
  return function (...rest) {
    return fn.apply(context, [...args, ...rest]);
  };
};

🔍 测试:

javascript 复制代码
function greet(greeting, name) {
  console.log(`${greeting}, ${name}. 我是 ${this.title}`);
}
const obj = { title: "手写大师" };

const bound = greet.myBind(obj, "你好");
bound("小吴"); // 你好, 小吴. 我是 手写大师

🔬 七、V8 字节码揭秘:箭头函数的 this 是怎么保存的?

在 V8 编译阶段:

  • 普通函数:创建自己的执行上下文和 this
  • 箭头函数:不创建自己的 this,从父级作用域捕获(Closure)
javascript 复制代码
function outer() {
  const arrow = () => {
    console.log(this); // 来自 outer 的 this
  };
  return arrow;
}
const fn = outer.call({ name: "Dora" });
fn(); // { name: 'Dora' }

V8 内部其实给你开了个"快照表",记录你当时的上下文,然后把箭头函数挂进去------这才是真正的闭包之力!


🧪 八、自测题 & 面试爆款

✅ 快问快答

1. 箭头函数的 this 来源?

✅ 定义时的词法作用域

2. 以下 this 最终绑定到?

javascript 复制代码
const obj = {
  name: "对象",
  fn: function () {
    return () => console.log(this.name);
  },
};
const f = obj.fn();
f(); // ?

✅ 输出:对象

🧠 思维题

为什么 bind 的函数还能被 new 打断?

  • 因为 new 会用新的对象替换掉绑定的 this,优先级最大

📋 this 专项面试自检 Checklist

能力点 自评 ✅ / ❌ 说明
我能准确解释五种 this 绑定规则 ✅ / ❌ 默认、隐式、显式(call/apply/bind)、new、箭头函数
我能在不用调试器的情况下判断任意 this 指向 ✅ / ❌ 看到代码就知道输出
我知道箭头函数为什么不能绑定自己的 this ✅ / ❌ 词法 this,无自己的 [[ThisBinding]]
我理解 new 绑定优先级为什么高于 bind ✅ / ❌ 构造器会创建新对象覆盖 this
我能画出 this 绑定优先级图解 ✅ / ❌ 可视化理解优先级
我能手写 bind 并支持参数预绑定 ✅ / ❌ 自定义 myBind + 测试用例
我知道 V8 引擎中 this 是如何处理的 ✅ / ❌ 理解 call record、执行上下文的 thisBinding
我能识别隐式绑定丢失的场景并修复 ✅ / ❌ 比如 setTimeout(obj.fn, 0) 情况
我能结合实际项目举出 this 绑定失效例子 ✅ / ❌ Vue/React 生命周期回调中的陷阱
我能编写包含各种绑定方式的综合测试用例 ✅ / ❌ 封装、组合调用、闭包、构造函数等
我理解 this 与执行上下文、作用域链的区别 ✅ / ❌ 三者常常混淆,能准确区分
我能快速讲清 this 与箭头函数在异步场景的区别 ✅ / ❌ 尤其是回调中箭头函数保留 this
我能结合 class / React / Vue 说明 this 应用 ✅ / ❌ 理解 class 中的 this 和常见误区

📌 建议打 ✅ 打满 12 项之后,再去冲击面试官的 this 十连问。否则很容易在"call/bind/new 混合调用"中翻车。

✅ Day 3 总结打卡(幽默加强版)

概念 内核理解 趣味比喻
默认绑定 没人领,this 指向 global 孤儿 this
隐式绑定 谁调用我我就是谁 同事你别跑
显式绑定 手动指定 this 明媒正娶
new 绑定 this 是新对象 亲爹最大
箭头函数 词法 this,死心眼 娃说:我不变
优先级 new > bind > call/apply > 隐式 > 默认 五段感情修罗场
V8 理解 this 是执行上下文的产物 call record + context
手写 bind this + 参数预绑定 稳住不慌的锁定器

📌 如果你已经能解释每一种 this 的来龙去脉 + 自己手写 bind + 看懂调用输出的 this 指向,那你已经是高级前端 70% 以上水平!

相关推荐
阿芯爱编程1 小时前
2025前端面试题
前端·面试
前端小趴菜052 小时前
React - createPortal
前端·vue.js·react.js
晓13132 小时前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
倔强青铜三3 小时前
苦练Python第18天:Python异常处理锦囊
人工智能·python·面试
菜包eo3 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
倔强青铜三3 小时前
苦练Python第17天:你必须掌握的Python内置函数
人工智能·python·面试
烛阴3 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
军军君013 小时前
基于Springboot+UniApp+Ai实现模拟面试小工具四:后端项目基础框架搭建下
spring boot·spring·面试·elementui·typescript·uni-app·mybatis
chao_7894 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼4 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript