JavaScript 中 new 运算符的底层原理与手动实现

21 阅读4分钟

在 JavaScript 中,new 是一个非常重要的关键字,它用于创建一个用户定义的对象类型的实例或内置对象类型的实例。理解 new 的底层执行机制,不仅有助于我们更好地掌握面向对象编程(OOP)思想,还能帮助我们在面试中或者开发中解决一些高级问题。


一、什么是 new

简单来说,当我们使用 new 关键字调用一个函数时,JavaScript 引擎会为我们自动完成以下操作:

  1. 创建一个新的空对象。
  2. 将该对象的原型指向构造函数的 prototype 属性。
  3. 将构造函数内部的 this 指向这个新对象。
  4. 执行构造函数中的代码。
  5. 如果构造函数返回的是一个对象,则返回该对象;否则返回新创建的对象。

二、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

正常工作。


五、对比原生 newmyNew

特性原生 newmyNew
创建新对象
绑定 this
继承原型链
构造函数返回对象时返回该对象
支持 Symbol 属性等复杂类型
支持 ES6 类(class)❌(需要额外处理)

注意:ES6 的 class 本质上是语法糖,其底层仍然是基于构造函数 + prototype 的机制。不过 class 不能直接作为普通函数调用,因此如果你要支持 class,还需要做一些额外判断。


六、进阶:支持 classmyNew

如果我们想让 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 绑定到这个新对象上并执行构造函数。最后根据构造函数的返回值决定最终返回的对象 —— 如果返回值是一个对象,则返回该对象,否则返回新创建的对象。