------小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 做了什么:
- 创建空对象
- 把 this 指到这个对象
- 执行函数
- 返回对象
注意:哪怕你 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% 以上水平!