揭秘 JavaScript 中的 "造物主"—— 手写 new 的那些事儿 🚀
一、new 是什么?—— 藏在代码里的 "造物主"🤖
如果你是 JavaScript 世界的 "造物主",想批量生产 "对象居民",那new就是你最趁手的工具!简单说,new是 JS 里的实例化运算符,专门用来从 "构造函数蓝图" 里造出具体的对象实例。
比如你定义了一个Person构造函数(相当于人类设计图),用new Person()一敲,一个活生生的 "人对象" 就诞生了。它就像 3D 打印机,输入设计图,输出实体模型,神奇吧?
二、日常使用 new:实例化的 "四步曲"👣
平时我们写let p = new Person('张三', 18)时,JS 引擎其实偷偷干了四件大事:
- 造空壳:先创建一个空对象
{}(相当于刚出生的空白娃娃) - 绑 this:让构造函数里的
this指向这个空对象(告诉娃娃:"你的属性要存在这里哦") - 连家谱:把空对象的
__proto__指向构造函数的prototype(给娃娃上户口,认祖归宗) - 送回家:默认返回这个加工好的对象(新鲜出炉的实例到手啦)
看看代码里的例子:
javascript
function Person(name, age) {
this.name = name; // this指向新对象,给对象加name属性
this.age = age; // 再加age属性
}
Person.prototype.species = '人类'; // 原型上的"家族属性"
let p = new Person('张三', 18);
console.log(p.name); // 张三(实例自己的属性)
console.log(p.species); // 人类(从原型链继承的属性)
这就是new的魔法:让对象既有自己的 "个性",又能继承 "家族共性"🔗
三、手写 myNew:山寨版 "造物主" 上线💻
知道了new的原理,我们也能自己造一个!先看第一种实现方式(用剩余参数...args):
javascript
// 模拟new功能的函数,Constructor是构造函数,args是参数列表
function objectFactory(Constructor, ...args) {
// 1. 造空壳:创建空对象
let obj = new Object();
// 2. 绑this:让构造函数的this指向obj,并执行构造函数
Constructor.apply(obj, args);
// 3. 连家谱:让obj的原型指向构造函数的原型
obj.__proto__ = Constructor.prototype;
// 4. 送回家:返回创建好的对象
return obj;
}
认清楚各个角色:
Constructor:就是你要实例化的构造函数(比如Person、Car这类 “蓝图函数”);obj:我们手动创建的空对象(new Object()生成的那个 “空白壳子”);apply:函数的内置方法,核心作用是改变函数执行时this的指向,并执行函数;args:传给构造函数的参数列表(比如['李四', 19])。
测试一下效果:
javascript
// 用自己写的objectFactory创建实例
let lisi = objectFactory(Person, '李四', 19);
console.log(lisi.age); // 19(成功继承构造函数里的属性)
console.log(lisi.species); // 人类(成功通过原型链找到属性)
完美复刻!这说明只要抓住 "四步曲",我们也能当 JS 的 "造物主"😎
四、类数组 Arguments:数组界的 "伪装者"🤡
在手写new的进阶版里,会遇到一个神奇的东西 ——arguments。这货长得像数组,却不是数组,堪称 JS 界的 "伪装大师"。
1. arguments 是什么?
arguments是函数运行时自动生成的参数对象,存储着传入函数的所有实参。比如:
javascript
function add() {
console.log(arguments); // 传入的所有参数都在这里
}
add(1, 2, 3); // arguments会是{0:1, 1:2, 2:3, length:3}
不管函数定义时有没有写参数,arguments都能把实参一网打尽,简直是函数的 "小跟班"👥
2. 类数组 vs 真数组:真假美猴王
arguments是典型的类数组,它和真数组的区别就像 "高仿鞋" 和 "正品鞋"—— 看着像,功能差很多:
| 特点 | 类数组 arguments | 真数组 [1,2,3] |
|---|---|---|
| 长度 | 有 length 属性 | 有 length 属性 |
| 索引 | 可通过下标访问(如 arguments [0]) | 可通过下标访问 |
| 数组方法 | 没有!不能用 reduce、map、join 等 | 有全套数组方法 |
看代码里的坑:
javascript
function add() {
// 报错!arguments没有reduce方法
return arguments.reduce((prev, cur) => prev + cur, 0);
}
认清楚各个角色:
reduce:数组的归并方法,遍历数组,把所有元素累加成一个值,只有真数组可以调用。(prev, cur) => prev + cur:reduce的回调函数,负责每次累加,其中:prev是上一次累加的结果(初始时是后面的0);cur是当前遍历到的数组元素;prev + cur是把累加值和当前值相加,作为下一次的prev。
这就是类数组的 "坑":别被它的外表骗了,它真的不是数组!
3. 类数组转真数组:"脱胎换骨" 术
想让arguments用上数组方法?得先给它 "换身衣服",变成真数组。常用的三种方法:
- 扩展运算符:
[...arguments](最简单直接) - Array.from:
Array.from(arguments)(ES6 新增,万能转换) - 数组借方法:
Array.prototype.slice.call(arguments)(老派但好用)
代码示例:
javascript
function add() {
// 转换为真数组
const args = [...arguments];
// 这下能用reduce了
return args.reduce((prev, cur) => prev + cur, 0);
}
console.log(add(1,2,3)); // 6(成功求和)
转换后,类数组就成了真正的数组,再也不用怕 "方法缺失" 的尴尬了😌
五、用 arguments 实现 new:进阶版 "造物主工具"🔧
知道了arguments的用法,我们可以写一个更灵活的new模拟器(不用剩余参数,兼容更多场景):
javascript
function objectFactory() {
// 1. 造空壳
let obj = new Object();
// 2. 从arguments里取出构造函数(第一个参数)
// 因为arguments是类数组,借数组的shift方法"移除并返回第一个元素"
let Constructor = [].shift.call(arguments);
// 3. 绑this:用剩下的arguments作为参数,执行构造函数
Constructor.apply(obj, arguments);
// 4. 连家谱
obj.__proto__ = Constructor.prototype;
// 5. 送回家
return obj;
}
搞明白:
1、[].shift.call(arguments) 是对 arguments 这个类数组做了局部的 “数组式操作” —— 借数组的 shift 方法操作类数组,而非把整个 arguments 转成真数组。咱们一步步拆解清楚。这行代码的本质是 “数组方法借调” ,不是 “类数组转真数组”,拆解成 3 层理解:
[]:创建一个空的真数组,目的是 “借” 它原型上的shift方法;.shift:数组的shift方法,作用是 “删除并返回数组的第一个元素”;.call(arguments):改变shift方法的this指向,让它操作arguments而非空数组。
2、你可能会问:apply 的第二个参数要求是 “数组 / 类数组”,这里传没转成真数组的 arguments 为啥不报错?
因为 apply 的设计本身就兼容类数组!apply 对第二个参数的要求是:“只要有 length 属性、能通过下标访问元素” 即可(类数组完全满足),不需要是严格的真数组。
测试一下:
javascript
let wangwu = objectFactory(Person, '王五', 20);
console.log(wangwu.name); // 王五(完美运行)
这种方式更灵活,不用提前声明参数,全靠arguments动态获取,是不是很秀?✨
六、面试官想考察什么?—— 透过现象看本质🔍
当面试官让你手写new时,他可不是想看你默写代码,而是在考察:
- 对原型链的理解:知不知道
__proto__和prototype的关系(这是 JS 面向对象的核心) - 对 this 绑定的掌握:能不能说清
apply如何改变this指向 - 对类数组的认知:是否了解
arguments的特性及转换方法 - 底层思维能力:会不会把复杂功能拆解成步骤(比如 "四步曲")
说白了,这道题是 "JS 基础综合体检",能看出你是不是真的理解 JS,而不是只会调用 API。
七、总结:从 "会用" 到 "懂原理" 的跨越🚀
今天我们从new的功能聊到实现,从类数组arguments的特性讲到转换,其实核心就一个:知其然,更要知其所以然。
new看似简单,背后藏着原型链、this 绑定的深层逻辑;arguments看似普通,却能帮我们理解 JS 的动态特性。当你能亲手实现这些 "内置功能" 时,就说明你已经从 "API 调用者" 升级成 "原理探索者" 了。
最后送大家一句:JS 的世界里,没有魔法,只有没看透的原理。下次再遇到new,不妨想想它背后的 "四步曲",说不定会有新发现!😉