手写实现 new 操作符

理解 JavaScript 中 new 操作符的工作原理,并手动实现一个功能完整的 myNew 函数

问题

在 JavaScript 中,new 操作符用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。理解 new 的内部机制对于掌握 JavaScript 面向对象编程至关重要。

本题要求手动实现 new 操作符的完整过程,包括:

  • 创建新对象
  • 设置原型链
  • 绑定 this 并执行构造函数
  • 处理构造函数的返回值

解答

/**
 * 手写实现 new 操作符
 * @param {Function} constructor - 构造函数
 * @param {...any} args - 传递给构造函数的参数
 * @returns {Object} 新创建的对象实例
 */
function myNew(constructor, ...args) {
  // 1. 参数校验:确保第一个参数是函数
  if (typeof constructor !== 'function') {
    throw new TypeError('constructor must be a function');
  }

  // 2. 创建一个新对象,并将其原型指向构造函数的 prototype
  const obj = Object.create(constructor.prototype);

  // 3. 将构造函数的 this 绑定到新对象上,并执行构造函数
  const result = constructor.apply(obj, args);

  // 4. 判断构造函数的返回值
  // 如果返回值是对象类型(包括函数),则返回该对象
  // 否则返回新创建的对象
  return (result !== null && typeof result === 'object') || typeof result === 'function'
    ? result
    : obj;
}

使用示例

// 示例 1: 基本使用
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}, ${this.age} years old.`);
};

const person1 = myNew(Person, 'Alice', 25);
console.log(person1.name); // 'Alice'
console.log(person1.age); // 25
person1.sayHello(); // 'Hello, I'm Alice, 25 years old.'
console.log(person1 instanceof Person); // true

// 示例 2: 构造函数返回基本类型(会被忽略)
function Dog(name) {
  this.name = name;
  return 123; // 返回基本类型会被忽略
}

const dog = myNew(Dog, 'Buddy');
console.log(dog.name); // 'Buddy'

// 示例 3: 构造函数返回对象类型(会被使用)
function Cat(name) {
  this.name = name;
  return { type: 'cat', sound: 'meow' }; // 返回对象会被使用
}

const cat = myNew(Cat, 'Kitty');
console.log(cat.name); // undefined
console.log(cat.type); // 'cat'
console.log(cat.sound); // 'meow'

// 示例 4: 验证原型链
function Animal(type) {
  this.type = type;
}

Animal.prototype.getType = function() {
  return this.type;
};

const animal = myNew(Animal, 'mammal');
console.log(animal.getType()); // 'mammal'
console.log(animal.__proto__ === Animal.prototype); // true

关键点

  • 创建新对象:使用 Object.create(constructor.prototype) 创建一个新对象,并自动将其 __proto__ 指向构造函数的 prototype

  • 绑定 this:使用 applycall 将构造函数内部的 this 绑定到新创建的对象上,确保构造函数中的属性和方法被正确添加到新对象

  • 执行构造函数:调用构造函数并传入参数,完成对象的初始化

  • 处理返回值:这是最容易被忽略的关键点

    • 如果构造函数显式返回一个对象类型(object 或 function),则返回该对象
    • 如果返回基本类型(string、number、boolean、null、undefined 等),则忽略返回值,返回新创建的对象
    • 如果没有返回值(undefined),则返回新创建的对象
  • 参数校验:确保传入的构造函数是一个有效的函数类型,提高代码的健壮性

  • 原型链继承:通过 Object.create() 确保新对象能够访问构造函数原型上的方法和属性,实现原型链继承