如何实现一个new操作符?

330 阅读2分钟

在具体实现new操作符之前,我们先来梳理一下new操作符是干啥的,以及具体做了什么事情

new操作符

引用一下MDN上的解释

new运算符是用来创建一个用户定义的对象的实例或具有构造函数的内置对象实例

简单来说,new运算符就是用来创建一个对象的实例,下面通过具体的例子说明:

function Car(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
}

const car1 = new Car('Eagle', 'Talon Tsi', 1993);

// 验证实例能够访问构造函数的属性
console.log(car1.make);
// Eagle

// 我们对car1进行一个验证
// 验证实例能够访问构造函数的原型链
car1.__proto__ === Car.prototype;  // true
car1.constructor === Car  // true
Car.prototype.constructor === Car // true

所以,这个new操作符主要的作用就是:

  • 让实例能够访问构造函数的属性
  • 让实例能够访问构造函数的原项链

实现一个简单的new

根据上面的分解,我们来整理一下简单的思路

  1. 创建一个临时对象
  2. 这个临时对象的__proto__ 指向原对象的prototype,使临时对象能够访问构造函数原型链上的属性
  3. 改变构造函数的this到这个临时对象,使临时对象能够访问构造函数的属性
  4. 返回这个新的对象
function _new(Obj, ...args) {
    var tmp = {};  // 创建一个临时对象
    tmp.__proto__ = Obj.prototype;  // 临时对象__proto__指向到构造函数的原型上,临时对象可以访问到构造函数原型的属性
    Obj.call(tmp, ...args);  // 改变构造函数的this到临时对象,这样临时对象就能访问到构造函数的属性
    return tmp;  // 返回这个临时对象
}

现在可以把这个简单的函数复制到控制台去验证:

function Car(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
}

const car1 = _new(Car, 'Eagle', 'Talon Tsi', 1993);

car1 instanceof Car // true
car.__proto__ === Car.prototype  // true
car1.constructor === Car  // true
Car.prototype.constructor === Car // true

验证已经通过拉

继续深入

上面一节只做了一个简单的new操作符实现,但是并没有考虑到更多的场景,如果构造函数有返回值的场景呢?

比如,构造函数本身就返回一个对象

function _new(Obj, ...args) {
    var tmp = {};
    tmp.__proto__ = Obj.prototype;
    Obj.call(tmp, ...args);
    return tmp;
}

function Car(make, model, year) {
    this.make = make;

    return {
        model,
        year
    };
}

var car1 = _new(Car, 'a', 'b', 'c');

console.log(car1.make)  // a
console.log(car1.model)  // undefined
console.log(car1.year);  // undefined

在这个例子中,构造函数返回一个对象,但是实例完全无法读取到这个对象,所以,需要对构造函数返回是不是个对象做一个判断

对之前初版代码进行改造:

function _new(Obj, ...args) {
    var tmp = {};
    tmp.__proto__ = Obj.prototype;
    const ret = Obj.call(tmp, ...args);
    return typeof ret === 'object' ? ret : tmp;  // 判断构造函数返回值是否是个对象,如果是对象,就返回这个对象,否则返回临时对象
}

最终版完成!