你以为 new 是语法糖?不,它是 JS 世界最神秘的“对象接生术”

46 阅读4分钟

一句话打透: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 接收构造函数内部的 this
  • obj.__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 真数组:本质区别是什么?

来看一个经典对比:

特性argumentsArray
length✔️✔️
索引访问✔️✔️
是不是 Array 实例✔️
instanceof Arrayfalsetrue
原型链不来自 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 的世界观。
在这里,一切对象,都必须经历一次“接生仪式”。