在 JavaScript 中,new
是一个非常重要的关键字,它用于创建一个用户定义的对象类型的实例或内置对象类型的实例。理解 new
的底层执行机制,不仅有助于我们更好地掌握面向对象编程(OOP)思想,还能帮助我们在面试中或者开发中解决一些高级问题。
一、什么是 new
?
简单来说,当我们使用 new
关键字调用一个函数时,JavaScript 引擎会为我们自动完成以下操作:
- 创建一个新的空对象。
- 将该对象的原型指向构造函数的
prototype
属性。 - 将构造函数内部的
this
指向这个新对象。 - 执行构造函数中的代码。
- 如果构造函数返回的是一个对象,则返回该对象;否则返回新创建的对象。
二、new 的执行过程详解(模拟)
我们可以将 new
的执行过程总结为如下伪代码逻辑:
function myNew(constructor, ...args) {
// 1. 创建一个空对象,并继承构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 绑定 this 到新对象并执行构造函数
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回了一个对象,则返回这个对象,否则返回新对象
return (typeof result === 'object' && result !== null) ? result : obj;
}
三、手动实现 myNew
函数
下面我们来一步一步地实现一个类似 new
功能的函数:myNew
。
实现目标:
- 支持传入构造函数和参数
- 创建一个继承自构造函数原型的新对象
- 构造函数中的
this
指向新对象 - 支持构造函数返回一个自定义对象的情况
示例代码:
function myNew(constructor, ...args) {
// 1. 创建一个空对象,并将其 __proto__ 指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 调用构造函数,并绑定 this 到新对象
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回的是一个对象,则返回该对象,否则返回新对象
if (result && typeof result === 'object') {
return result;
}
return obj;
}
四、测试案例
我们来写几个测试用例,验证我们的 myNew
是否能正常工作。
测试案例 1:基础构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const p1 = new Person('Alice', 20);
const p2 = myNew(Person, 'Bob', 25);
console.log(p1); // Person { name: 'Alice', age: 20 }
console.log(p2); // Person { name: 'Bob', age: 25 }
p1.sayHello(); // Hello, I'm Alice
p2.sayHello(); // Hello, I'm Bob
输出结果一致,说明基本功能已经实现。
测试案例 2:构造函数返回一个对象
有时候构造函数会显式返回一个对象,这时候 new
应该返回这个对象而不是默认创建的对象。
function Foo() {
this.value = 42;
return { value: 99 };
}
const f1 = new Foo();
const f2 = myNew(Foo);
console.log(f1.value); // 99
console.log(f2.value); // 99
我们的 myNew
正确处理了这种情况。
测试案例 3:构造函数返回非对象值
如果构造函数返回的不是对象(比如数字、字符串、undefined),则应该忽略返回值,返回新对象。
function Bar() {
this.value = 100;
return 200;
}
const b1 = new Bar();
const b2 = myNew(Bar);
console.log(b1.value); // 100
console.log(b2.value); // 100
正常工作。
五、对比原生 new
和 myNew
特性 | 原生 new | myNew |
---|---|---|
创建新对象 | ✅ | ✅ |
绑定 this | ✅ | ✅ |
继承原型链 | ✅ | ✅ |
构造函数返回对象时返回该对象 | ✅ | ✅ |
支持 Symbol 属性等复杂类型 | ✅ | ✅ |
支持 ES6 类(class) | ✅ | ❌(需要额外处理) |
注意:ES6 的
class
本质上是语法糖,其底层仍然是基于构造函数 + prototype 的机制。不过class
不能直接作为普通函数调用,因此如果你要支持class
,还需要做一些额外判断。
六、进阶:支持 class
的 myNew
如果我们想让 myNew
支持 ES6 的类,可以稍微修改一下代码:
function myNew(constructor, ...args) {
// 检查是否是 class
if (typeof constructor !== 'function') {
throw new TypeError('Constructor is not a function');
}
// 创建新对象,继承构造函数/类的 prototype
const obj = Object.create(constructor.prototype);
// 执行构造函数
const result = constructor.apply(obj, args);
// 返回构造函数返回的对象或新对象
return (result && typeof result === 'object') ? result : obj;
}
使用示例:
class Animal {
constructor(name) {
this.name = name;
}
}
const dog = myNew(Animal, 'Buddy');
console.log(dog.name); // Buddy
成功支持 class
!
七、总结
通过本文,你应该已经掌握了:
new
的底层执行流程;- 如何手动实现一个类似
new
的功能; new
在遇到构造函数返回对象时的行为;- 如何扩展
myNew
以支持 ES6 的class
; - 理解了原型链继承的基本原理。
💡 面试高频题补充
问:请解释 new 关键字的底层实现?
答:
new
关键字会创建一个新对象,并将其原型指向构造函数的 prototype,接着把构造函数的 this 绑定到这个新对象上并执行构造函数。最后根据构造函数的返回值决定最终返回的对象 —— 如果返回值是一个对象,则返回该对象,否则返回新创建的对象。