JavaScript 中 new 的实现原理

1,221 阅读3分钟
1. new 的原理

JavaScript 中 new 关键词的作用就是执行构造函数,然后返回一个实例对象;在 new 的过程中,根据构造函数的定义,来确定是否需要传递参数。

function Dog(name) {
  this.name = name;
  this.color = '黑色';
}
const d = new Dog('小黑');
console.log(d.color);     // 黑色
console.log(d.name);    // 小黑

这段代码很容易理解,d 是通过 Dog 这个构造函数生成的一个实例对象。

再来看看一个示列,把上面示列的 new 关键词去掉

function Dog(name) {
  this.name = name;
  this.color = '黑色';
}
const p = Dog('小黑');
console.log(color);       // 黑色
console.log(name);        // 小黑
console.log(d);           // undefined
console.log(d.color);     // TypeError: Cannot read property 'color' of undefined
console.log(d.name);     // TypeError: Cannot read property 'name' of undefined

从上面代码中可以看到,去掉 new 关键词后,执行 Dog 函数返回的是 undefind。在上面的代码中,this 的指向是全局的 window 对象,所以执行 Dog 其实是给全局环境的 namecolor 属性赋值了。

那么 new 在生成实例对象过程中到底执行了哪些步骤呢?

总结下来,执行的步骤大致分为以下几步:

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(this 指向新创建的对象)
  3. 执行构造函数的代码(给新对象添加属性)
  4. 返回新对象
2. 构造函数中有 return

在日常写代码中,我们基本上不会在构造函数中加 return;那么如果构造函数有 return,结果会怎么样呢?

function Dog(name) {
  this.name = name;
  return { color: '黑色' };
}
const d = new Dog('小黑');
console.log(d);           // { color: '黑色' }
console.log(d.name);      // undefined
console.log(d.color);     // 黑色

通过这段代码我们可以看出,当构造函数 return 出来的是一个对象时,new 命令会直接返回构造函数 return 的对象,而不是通过 new 执行生成的与构造函数相关的新对象。

如果构造函数返回的不是对象,那么结果又会是怎样呢?

function Dog(name) {
  this.name = name;
  return '黑色';
}
const d = new Dog('小黑');
console.log(d);           // { name: '小黑' }
console.log(d.name);      // 小黑

可以看出,当构造函数 return 的不是对象,结果还是会根据 new 关键词的逻辑执行,返回构造函数的实例对象。

总结:执行 new 关键词后,总是得到一个对象,要么是构造函数的实例对象,要么是构造函数 return 语句指定的对象

3. new 的实现

new 的实现过程中,要注意以下两点:

  1. 实例对象可以访问私有属性
  2. 实例对象可以访问构造函数原型(constructor.prototype)所在原型链上的属性
const _new = (fn, ...rest) => {
  const nObj = Object.create(fn.prototype);
  const result = fn.call(nObj, ...rest);
  return (typeof result === 'object') ? result : nObj;
};

函数 Object.create(proto,[propertiesObject]) 的作用

Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__

proto

必须,新创建的对象的原型对象;该参数会被赋值给新对象的 __proto__

propertiesObject

可选。需要传入一个对象,该对象的属性类型参照 Object.defineProperties() 的第二个参数。传入的对象添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。

const _new = (fn, ...rest) => {
  const nObj = Object.create(fn.prototype);
  const result = fn.call(nObj, ...rest);
  return (typeof result === 'object') ? result : nObj;
};
function Dog(name) {
  this.name = name;
  this.color = '黑色';
}
const d = _new(Dog, '小黑');
console.log(d.color);     // 黑色
console.log(d.name);    // 小黑