一句话打透:new 是 JavaScript 面向对象的“接生婆”。
它接生对象、绑定 this、搭好原型链,甚至决定你对象能不能继承人类文明。
当你敲下:
const p = new Person('张三')
你以为 JS 只是“帮你创建个对象”吗?
不,它背后是一套严谨得像玄学仪式一样的流程。
今天我们就用手写 new为主线,用类数组 arguments作为支线,一起通关:
- 🧬 new 的底层原理到底干了什么?
- 🏺 为什么手写 new 能复刻官方行为?
- 🧩 arguments 为什么“像数组但不是数组”?
- 🧠 原型链和 proto 在 new 里扮演了什么角色?
这篇文章保证你:
- 看懂 JS OOP 底层
- 搞明白 new 的魔法流程
- 理解 arguments 的本质
- 顺便拥有面试杀手锏
🍭 一、你以为 new 是语法糖?其实它是 JS 的“对象接生仪式”
在 JavaScript 里,对象不是凭空出现的。
它们都要经过 new 构造函数 这个仪式。
用一句话解释:
new 做的事:
创建一个空对象 → 绑定 this → 执行构造函数 → 连接原型链 → 返回实例。
我们改成更抓眼球的版本:
new = 创建婴儿(obj) + 把构造函数当产房(apply) +
给婴儿认祖先(原型链 proto) + 把婴儿交给你(return)。
下面是 new 的底层流程图:
new Person()
│
├── 1. 创建空对象:obj = {}
│
├── 2. 绑定 this:Person.apply(obj)
│ (构造函数内部给 obj 填属性)
│
├── 3. 认原型祖先:obj.__proto__ = Person.prototype
│
└── 4. 返回 obj
是不是很像一个正规又严谨的“对象接生流程”?
而这个流程,你完全可以自己写出来!
🎭 二、实现一个手写 new:objectFactory
让我们手写一个 objectFactory,复刻 new 的行为。
function objectFactory() {
var obj = new Object(); // 1. 创建空对象
var Constructor = [].shift.call(arguments);
// 2. 拿到构造函数
Constructor.apply(obj, arguments);
// 3. 绑定 this,执行构造函数
obj.__proto__ = Constructor.prototype;
// 4. 新对象认祖先:原型链
return obj; // 5. 完成!返回实例
}
使用方式跟 new 一模一样:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(){
console.log(`你好,我是${this.name}`);
};
let p = objectFactory(Person, '张三', 18);
console.log(p.name); // 张三
p.sayHi(); // 你好,我是张三
🔔 重点来了:
🧠 为什么这个写法完美模拟 new?
obj接收构造函数内部的 thisobj.__proto__ = Constructor.prototype
让新对象能访问原型链上的方法(如 sayHi)- 最终返回 obj
也就是说:
new Person() 和
objectFactory(Person)
内部流程几乎一模一样!
恭喜你:
你已经理解 new 的底层运行机制了。
(这部分在面试里是非常高频的!)
🧩 三、类数组 arguments:像数组,却不能点 reduce
看你给的代码:
console.log(JSON.stringify(arguments));
console.log(JSON.stringify([1,2,3]));
你会发现:
arguments看起来跟数组很像- 但是你不能用
arguments.reduce
为什么?
🚨 因为 arguments 是 类数组对象(Array-like)
它具备:
length- 索引访问能力(arguments[0])
- 可遍历
- 但它不是 Array 实例
对比一下:
console.log(Object.prototype.toString.call(arguments));
// [object Arguments]
console.log(Object.prototype.toString.call([1,2,3]));
// [object Array]
看到没?
它甚至有自己专属的“身份证”。
🧬 四、类数组 VS 真数组:本质区别是什么?
来看一个经典对比:
| 特性 | arguments | Array |
|---|---|---|
| length | ✔️ | ✔️ |
| 索引访问 | ✔️ | ✔️ |
| 是不是 Array 实例 | ❌ | ✔️ |
| instanceof Array | false | true |
| 原型链 | 不来自 Array.prototype | 来自 Array.prototype |
| 能否使用 reduce/map/push | ❌ | ✔️ |
总结一句话:
arguments“长得像”,但血统不是 Array,所以没有数组方法。
这就是为什么下面会报错:
arguments.reduce(...) // ❌
🪄 五、那怎么把 arguments 变成真正的数组?
有三种方式:
✅ 1. 扩展运算符(最推荐)
const args = [...arguments];
✅ 2. Array.from
const args = Array.from(arguments);
✅ 3. call + slice 古法
const args = [].slice.call(arguments);
转换后的 args 就能:
args.reduce((a,b)=>a+b)
args.map(...)
🧠 六、为什么手写 new 离不开 arguments?
因为我们写的 new 仿制品:
var Constructor = [].shift.call(arguments);
Constructor.apply(obj, arguments);
需要:
- 第一个参数拿到构造函数
- 后续参数作为构造函数参数
- apply + arguments 完美传参
类数组 + apply 天然适合这个任务。
也就是说:
arguments:支撑手写 new 的血液。
new:构造对象的灵魂。
原型链:对象继承能力的脊梁。
三者共同组成 JS OOP 的核心系统。
🎉 七、总结:理解 new,你就读懂了 JavaScript 的 OOP 底层
这篇文章我们搞懂了:
🟦 1. new 到底做了什么(接生流程)
- 创建空对象
- 绑定 this
- 应用构造函数
- 连接原型链
- 返回实例
🟧 2. 手写 new
你已经能写出:
function objectFactory(...) {...}
这在面试中属于“直接加分项”。
🟩 3. arguments 的本质:类数组对象
- 像数组,但不是数组
- 不能用数组方法
- 转换方法有 3 种
🟪 4. 原型链在 new 里扮演的关键角色
obj.__proto__ = Constructor.prototype- 实例能访问“父类”方法
🔥 最后一句:
理解手写 new 的那一刻,你就真正走进了 JS 的世界观。
在这里,一切对象,都必须经历一次“接生仪式”。