手写 `new`:揭开 JavaScript 实例化背后的秘密

在 JavaScript 中,new 是一个看似简单却承载着面向对象核心机制的关键字。当我们写下 new Person() 时,引擎并非只是"调用一个函数",而是在背后完成了一系列精密的初始化操作:创建对象、绑定原型、执行构造逻辑、返回实例。理解这一过程,不仅能帮助我们掌握原型继承的本质,也为实现高级框架(如 Vue 的响应式系统)打下基础。本文将从零开始,手写一个 new 的模拟函数,并深入剖析其每一步的作用。

new 到底做了什么?

使用 new 调用构造函数时,JavaScript 引擎会按以下顺序执行:

  1. 创建一个全新的空对象;
  2. 将该对象的 __proto__ 指向构造函数的 prototype;
  3. 将构造函数内部的 this 绑定到这个新对象,并执行函数体;
  4. 如果构造函数没有显式返回一个对象,则自动返回新创建的对象。

这四步构成了 JavaScript 原型式面向对象的基石。为了验证我们的理解,可以尝试手动复现这一过程。

手写 objectFactory 模拟 new

ini 复制代码
function objectFactory() {
  const obj = {};
  const Constructor = Array.prototype.shift.call(arguments);
  obj.__proto__ = Constructor.prototype;
  Constructor.apply(obj, arguments);
  return obj;
}

这段代码精炼地还原了 new 的行为。首先,通过 shift 从 arguments 中取出第一个参数(即构造函数),其余参数作为实参传递给构造函数。接着,将新对象的原型链指向构造函数的 prototype,确保实例能访问其方法。最后,用 apply 将 this 绑定到 obj 并执行构造逻辑。

测试如下:

ini 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHi = function() {
  console.log('你好,我是' + this.name);
};

const p1 = new Person('张三', 18);
const p2 = objectFactory(Person, '张三', 18);
console.log(p1.sayHi === p2.sayHi); // true

两个实例不仅属性一致,连原型方法也完全共享,证明我们的模拟是成功的。

关于 arguments:类数组的灵活与局限

在 objectFactory 中,我们使用了 arguments 对象来获取传入的所有参数。arguments 是函数内部的一个类数组对象 :它拥有 length 属性,可通过索引访问每个参数,但不具备数组的原生方法(如 map、reduce)。

javascript 复制代码
function add() {
  const args = [...arguments];
  return args.reduce((sum, val) => sum + val, 0);
}
console.log(add(1, 2, 3)); // 6

通过扩展运算符 [...arguments],我们可以将其转换为真正的数组,从而使用现代数组方法。这种灵活性使得 JavaScript 函数天然支持可变参数,无需预先声明参数个数。

为什么需要手动处理 arguments?

在模拟 new 时,无法预知用户会传入多少个参数,也无法提前定义形参列表。因此,必须依赖 arguments 动态获取所有实参。而 Array.prototype.shift.call(arguments) 是一种经典技巧:它将 arguments 视为数组,从中"弹出"第一个元素(构造函数),剩余部分自然成为构造函数的参数列表。

值得注意的是,arguments 并非真正的数组,其 __proto__ 指向 Object.prototype,而非 Array.prototype。这也是为何不能直接调用 arguments.shift()------必须借助 call 或 apply 借用数组的方法。

返回值的边界情况

标准 new 运算符还有一个细节:如果构造函数显式返回一个对象,则忽略新创建的实例,直接返回该对象。例如:

javascript 复制代码
function Test() {
  this.value = 1;
  return { value: 2 };
}
console.log(new Test().value); // 2

我们的 objectFactory 目前未处理此情况。若要完全兼容,可补充判断:

ini 复制代码
const result = Constructor.apply(obj, arguments);
return (typeof result === 'object' && result !== null) ? result : obj;

但在大多数实际场景中,构造函数不返回值或仅返回基本类型,因此简化版已足够使用。

总结

手写 new 不仅是一道常见的面试题,更是深入理解 JavaScript 对象模型的关键实践。通过模拟其实例化过程,我们清晰地看到:对象的创建、原型的链接、上下文的绑定 是如何协同工作的。同时,对 arguments 的操作也展示了 JavaScript 在参数处理上的动态特性。

尽管现代开发中 class 语法已普及,但其底层依然依赖 new 和原型链。掌握这些原始机制,意味着你不仅能写出更健壮的代码,还能在阅读框架源码、调试复杂继承关系时游刃有余。毕竟,真正的 JavaScript 功力,往往藏在这些"基础却深刻"的细节之中。

相关推荐
用户831348593069822 分钟前
Vue3 事件:常用用法与开发高频坑点
前端·javascript·vue.js
漫步人生走在路上1 小时前
用小语种SEO突破外贸B2B天花板:法语、德语市场的实测数据
开发语言·前端·javascript·人工智能·搜索引擎·ecmascript
ydjs678899-bot2 小时前
您的Next.js中间件匹配器配置可能会跳过您认为它保护的路由
开发语言·javascript·中间件
Chengbei112 小时前
JsOwl|AI 赋能 JS 敏感信息扫描器,AST + 正则、source‑map 源码还原、登录态爬虫输出交互式 HTML 报告
开发语言·javascript·网络·人工智能·爬虫·网络安全·自动化
ShineWinsu3 小时前
对于JavaScript:初识JavaScript以及输入输出
java·开发语言·前端·javascript·css·面试·html
小白学大数据3 小时前
逆向分析短视频平台 a_bogus 参数:从 JavaScript 混淆到 Python 复现
javascript·python·音视频
donk13144 小时前
React Native for OpenHarmony 实战:三方库 react-native-circular-action-menu 的鸿蒙化适配指南
javascript·分布式·react native·react.js·wpf·harmonyos
ydjs678899-bot4 小时前
为什么大型monorepos变慢了Rush.js + Sparo对此做了什么
开发语言·javascript·ecmascript
will_net4 小时前
Vue IIS部署 api 访问当前站点 代理跳转真实API 问题
前端·javascript·vue.js
weixin_449173654 小时前
react 的useEffect用法
前端·javascript·react.js