new一个对象的过程

166 阅读2分钟
function Car(color) {
    this.color = color;
}
Car.prototype.start = function() {
    console.log(this.color + " car start");
}

var car = new Car("black");
car.color; // 访问构造函数里的属性
// black

car.start(); // 访问原型里的属性
// black car start

new 创建的实例有以下2个特性:

  1. 可以访问到构造函数里的属性
  2. 访问到原型里的属性

当 new Car(...) 执行时,会发生一下事情:

  1. 创建一个新对象并使它继承 Car.prototype
  2. 绑定 this
  3. 如果构造函数有显式返回一个对象,则返回这个对象,如果没有,返回新对象

实现过程:

  1. 创建一个空对象

    var obj = new Object();
    
  2. 设置新对象的__proto__属性指向构造函数的prototype对象

    obj.__proto__ = foo.prototype
    
  3. 使用新对象调用函数,函数中的this被指向新的实例对象

    var result = foo.call(obj);
    
  4. 执行构造函数,并返回创建的对象

    若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象(obj);若返回值是引用类型的值,则实际返回值为这个引用类型。

    if (typeof(result) == "object") {
      func = result;
    } else {
      func = obj;
    }
    

以上代码总结起来就是:

function create() {
    var obj = new Object();
    // 可以访问原型链上的属性
    obj.__proto__ = foo.prototype;
    // 绑定 this 可以访问到构造函数上的属性
    var result = foo.call(obj);
    return typeof(result) == "object" ? result : obj;
}

但以上方法存在一定缺陷。

通过现代浏览器的操作属性的便利性,可以改变一个对象的 [[Prototype]] 属性, 这种行为在每一个JavaScript引擎和浏览器中都是一个非常慢且影响性能的操作,使用这种方式来改变和继承属性是对性能影响非常严重的,并且性能消耗的时间也不是简单的花费在 obj.proto = ... 语句上, 它还会影响到所有继承来自该 [[Prototype]] 的对象,如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]]。相反, 创建一个新的且可以继承 [[Prototype]] 的对象,推荐使用 Object.create() —MDN

且,没考虑参数传入的情况。所以,进行优化一下。

function create() {
    // 获得构造函数并删除 arguments 的第一个参数(即传入的构造函数)
    var Con = [].shift.call(arguments);
    // 创建一个新对象,并链接到原型,obj可以访问构造函数原型中的属性
    var obj = Object.create(Con.prototype);
    // 绑定 this 实现继承,obj可以访问到构造函数中的属性,且传入对应的参数
    var res = Con.apply(obj, arguments);
    // 返回对象
    return res instanceof Object ? res : obj;
}

参考:

深度解析 new 原理及模拟实现