手写 `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 的行为。首先,通过 shiftarguments 中取出第一个参数(即构造函数),其余参数作为实参传递给构造函数。接着,将新对象的原型链指向构造函数的 prototype,确保实例能访问其方法。最后,用 applythis 绑定到 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 属性,可通过索引访问每个参数,但不具备数组的原生方法(如 mapreduce)。

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()------必须借助 callapply 借用数组的方法。

返回值的边界情况

标准 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 功力,往往藏在这些"基础却深刻"的细节之中。

相关推荐
用户2367829801684 小时前
从零实现 GIF 制作工具:LZW 压缩与 Median Cut 色彩量化
前端·javascript
棉猴4 小时前
Python海龟绘图之绘制文本
javascript·python·html·write·turtle·海龟绘图·输出文本
Highcharts.js4 小时前
线形比赛积分增长或竞赛图|Highcharts企业图表代码示列
开发语言·前端·javascript·折线图·highcharts·竞赛图
让学习成为一种生活方式5 小时前
大肠杆菌合成扑热息痛--对乙酰氨基酚--文献精读227
开发语言·前端·javascript
多秋浮沉度华年5 小时前
electron 初始使用记录
javascript·arcgis·electron
竹林8185 小时前
用 wagmi v2 + WebSocket 硬磕 NFT 上架失败:一个前端开发者踩过的实时状态同步坑
javascript·next.js
豹哥学前端5 小时前
告别割裂式学习:待办清单项目,一次性掌握数组、本地存储与事件委托
前端·javascript
JYeontu5 小时前
照片墙太死板?做一个会随风摇摆的绳串图片交互效果
前端·javascript·css
Yue栎廷5 小时前
邪修:Markdown加粗语法**本土化改造
前端·javascript·人工智能
小歪 | 前端6 小时前
VUE_运行Vue项目Network: unavailable问题解决
前端·javascript·vue.js