手写 `new`、`call`、`apply`、`bind` + V8 函数调用机制解密

🌀 小Dora 的 JS 修炼日记 · Day 7

"写 polyfill,不只是为了面试,而是走进 JS 引擎脑子里的最近通道。"

------dora · 准高级前端工程师


🌟 开篇:四大函数机制是怎么被 JS 引擎执行的?

  • new:你以为只是创建对象?其实背后隐藏了 V8 的 Hidden Class 分配 + 构造绑定策略
  • call / apply:你以为只是换个 this?其实是 JS 上下文切换 + Inline Cache 的血泪史
  • bind:你以为是懒执行?其实 V8 想优化都优化不了,还会禁用内联!

我们要掌握的,不只是 API 行为,而是 👇

函数机制 执行过程 V8 处理重点 性能影响
new 创建对象 + 构造函数调用 HiddenClass 状态迁移 构造对象不一致会触发 deopt
call/apply 上下文切换,立即调用 Inline Cache 路径匹配 多变 this 会失去优化
bind 延迟绑定 this 返回闭包 函数不可预测性高 V8 无法内联,优化死角

🔧 一、手写 new 操作符 + 底层流程

javascript 复制代码
function myNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype); // 模拟原型链挂载
  const result = Ctor.apply(obj, args);      // 执行构造函数
  return result instanceof Object ? result : obj;
}

🧠 底层发生了什么?

  1. 申请内存空间(堆)
  2. 创建隐藏类(Hidden Class)
  3. this 绑定到新对象
  4. 构造函数执行
  5. 返回对象

🐛 示例陷阱题:

ini 复制代码
function A() {
  this.name = '小吴';
  return { age: 26 };
}
const res = myNew(A);
console.log(res.name); // ❓ undefined or 小吴?

✅ 解析:构造函数返回对象,会覆盖 new 创建的 this,所以 name 是 undefined。


🔧 二、手写 call / apply

ini 复制代码
Function.prototype.myCall = function(ctx, ...args) {
  ctx = ctx || globalThis;
  const fn = Symbol();
  ctx[fn] = this;
  const result = ctx[fn](...args);
  delete ctx[fn];
  return result;
};

👇 题目验证理解:

javascript 复制代码
function say(a, b) {
  console.log(this.name, a, b);
}
const obj = { name: '小吴' };

say.call(obj, 'Hello', 'World'); // 小吴 Hello World
say.myCall(obj, 'Hi', 'V8');     // 小吴 Hi V8

✅ call/apply 的实质:临时把函数挂在 obj 上执行,然后删除


🔧 三、手写 bind

javascript 复制代码
Function.prototype.myBind = function(ctx, ...args) {
  const originFn = this;
  function bound(...restArgs) {
    const finalCtx = this instanceof bound ? this : ctx;
    return originFn.apply(finalCtx, [...args, ...restArgs]);
  }
  bound.prototype = Object.create(originFn.prototype);
  return bound;
};

🧪 测试继承 + 构造:

ini 复制代码
function Person(name) {
  this.name = name;
}
const BoundPerson = Person.myBind({});
const p = new BoundPerson('小吴');
console.log(p.name); // ✅ 小吴

📛 误区题目:

ini 复制代码
const obj = { name: 'V8' };
function foo() {
  console.log(this.name);
}
const bound = foo.bind(obj);
const newFoo = new bound();

🔍 注意:当用 new 调用 bind 结果时,this 会忽略绑定的 obj,绑定到新创建对象。


🔬 四、V8 背后的执行模型(执行栈 + Hidden Class)

  • call/apply 会触发函数上下文切换:push stack → bind this → run → pop
  • bind 返回闭包,闭包结构复杂,V8 无法内联展开,性能差
  • new 会判断构造函数是否符合 inline 构造路径(不能随意返回对象!)

Hidden Class 的影响:

ini 复制代码
function A() {
  this.x = 1;
}
const a1 = new A();
const a2 = new A();
a1.y = 2; // ⚠️ 改变 Hidden Class,性能损

🔍 五、典型面试题 + 实战题自测

题 1:手写一个组合继承函数

ini 复制代码
function Parent(name) {
  this.name = name;
}
Parent.prototype.say = function() {
  return this.name;
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

👉 这段代码能否被 V8 优化?为何?

题 2:输出结果分析

scss 复制代码
function Foo() {}
Foo.prototype.test = function() {
  console.log(this);
};
const f = new Foo();
const test = f.test;
test();       // ❓
f.test();     // ❓
test.call(f); // ❓

题 3:bind 后还能再 call 吗?

ini 复制代码
function test() {
  console.log(this.name);
}
const obj = { name: '小吴' };
const bindFn = test.bind(obj);
bindFn.call({ name: 'V8' }); // 输出?

✅ 输出是 "小吴",因为 bind 优先级更高。


📋 📌 函数调用机制专项自查 Checklist

自查点 是否掌握
能否手写 new 实现并解释 proto 绑定
知道 call/apply 原理及内存释放机制
bind 的延迟执行及构造 this 替换规则
V8 中 call 的 Inline Cache 原理
bind 无法内联优化的底层限制
函数上下文与 this 的绑定顺序
构造函数返回对象 VS 返回原始值
new + bind 的优先级理解

💡 总结一句话

call 是函数扮演别人,apply 是换衣服一起上,
bind 是懒汉型"认主",而 new 是新生儿找爹。

而 V8 背后,会对每个调用路径打上优化或惩罚标签,你写的每一行代码,都在引擎眼皮底下"评优评级"

相关推荐
这是个栗子9 分钟前
【问题解决】npm包下载速度慢
前端·npm·node.js
amazinging12 分钟前
北京-4年功能测试2年空窗-报培训班学测开-第五十四天
python·学习·面试
Komorebi_999914 分钟前
数组和对象的深拷贝和浅拷贝的方法
前端·web
weixin_5841214316 分钟前
vue3+ts+elementui-表格根据相同值合并
前端·javascript·elementui
吃手机用谁付的款1 小时前
HTML常见标签
前端·html
好好研究1 小时前
CSS样式中的布局、字体、响应式布局
前端·css
拉不动的猪3 小时前
前端小白之 CSS弹性布局基础使用规范案例讲解
前端·javascript·css
伍哥的传说3 小时前
React强大且灵活hooks库——ahooks入门实践之开发调试类hook(dev)详解
前端·javascript·react.js·ecmascript·hooks·react-hooks·ahooks
界面开发小八哥3 小时前
界面控件Kendo UI for Angular 2025 Q2新版亮点 - 增强跨设备的无缝体验
前端·ui·界面控件·kendo ui·angular.js
枷锁—sha4 小时前
从零掌握XML与DTD实体:原理、XXE漏洞攻防
xml·前端·网络·chrome·web安全·网络安全