手写 new 操作符

实现 JavaScript 中 new 操作符的执行过程

问题

手写实现 new 操作符的执行过程。

解答

new 操作符做了什么

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};

const p = new Person('Tom', 18);

执行 new Person('Tom', 18) 时,发生了以下步骤:

  1. 创建一个空对象
  2. 将空对象的原型指向 Person.prototype
  3. 执行 Personthis 绑定到新对象
  4. 返回新对象(如果构造函数返回了对象,则返回该对象)

实现 myNew

function myNew(Constructor, ...args) {
  // 1. 创建空对象,原型指向构造函数的 prototype
  const obj = Object.create(Constructor.prototype);
  
  // 2. 执行构造函数,this 绑定到新对象
  const result = Constructor.apply(obj, args);
  
  // 3. 如果构造函数返回对象,则返回该对象;否则返回新创建的对象
  return result instanceof Object ? result : obj;
}

测试

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};

const p1 = myNew(Person, 'Tom', 18);
console.log(p1.name); // 'Tom'
console.log(p1.age);  // 18
p1.sayHi();           // "Hi, I'm Tom"
console.log(p1 instanceof Person); // true

// 测试构造函数返回对象的情况
function Foo() {
  this.a = 1;
  return { b: 2 };
}

const f = myNew(Foo);
console.log(f.a); // undefined
console.log(f.b); // 2

不用 Object.create 的写法

function myNew(Constructor, ...args) {
  // 创建空对象
  const obj = {};
  
  // 设置原型
  obj.__proto__ = Constructor.prototype;
  // 或者用:Object.setPrototypeOf(obj, Constructor.prototype)
  
  // 执行构造函数
  const result = Constructor.apply(obj, args);
  
  // 判断返回值
  return result instanceof Object ? result : obj;
}

关键点

  • 新对象的原型要指向构造函数的 prototype
  • applycall 绑定 this 并执行构造函数
  • 构造函数返回对象时,new 的结果是该对象,而非新创建的对象
  • 构造函数返回基本类型时,忽略返回值,仍返回新对象
  • Object.create(proto) 可以一步完成创建对象和设置原型