模拟实现new关键字

2,129 阅读3分钟

new做了什么

要实现new关键字, 我们首先要知道new都做了什么事情, 红宝书(JavaScript高级程序设计第三版)第145页中(没错我翻书了, ^_^)是这样描述的:

  • 创建一个对象
  • 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  • 执行构造函数中的代码(为这个新对象添加属性)
  • 返回新对象

对于已经了解的人来说,上面的四步没有任何问题,描述也准确无误, 但是我一个萌新上来就说这个? 创建一个对象? 是个什么样对象? 作用域赋给新对象?...

懵逼

下面我们就结合实际的例子来一起理解一下,这四步到底做了什么

我们先看我们平时定义构造函数以及调用构造函数的姿势, 然后尝试从结果反推new的执行过程

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayHi = function() {console.log('My name is great ', this.name)};
const p = new Person('Alex', 22); // {name: 'Alex', age: 22}  

首先定义了一个Person构造函数, 该构造函数有两个参数(name, age)以及定义在原型上的一个方法sayHi, 使用new关键字执行了Person后, 我们得到了一个对象P, 并且p上面有 name 以及 age 两个属性; 同时我们也发现, p可以访问到原型上的 sayHi方法, 基于我们看到的现象我们下面一步步模拟new的执行过程

js中我们无法模拟new的执行方式, 我们可以使用函数来模拟实现过程, 因为new的时候执行了构造函数, 所以我们第一个参数传入一个构造函数constructor, 后面的参数作为constructor执行时需要的参数:

let myNew = function (constructor) {
   
}

new执行后得到的是一个对象, 所以我们的函数执行后也要返回一个对象:

let myNew = function (constructor) {
    // 创建一个对象
    let instance = {};
    // 返回新对象
    return instance;
}

返回出来的instance就相当于我们示例中的p, p可以访问构造函数上的原型, 所以我们需要将创建的对象instance__proto__属性指向构造函数的原型,这样得到的instance才能访问到构造函数的原型(这一步不大清楚的同学可以先看看原型链的概念)

let myNew = function (constructor) {
    // 创建一个对象
    let instance = {};
    instance.__proto__ = constructor.prototype;
    // 返回新对象
    return instance;
}
console.log(myNew(Person, 'Alex', 22)) //{}

到了这里, 我们已经能理解第一步(创建一个对象),和第四步了(返回新对象), 但当前实现的myNew执行以后返回的是仍旧是一个空对象, 和我们预期的不符,缺少了必要的属性nameage, 我们继续观察一下构造函数

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

传入的参数最终都赋值给了构造函数this的属性, 如果能将将this改成instance不就可以了吗? 使用apply改变this的指向

let myNew = function (constructor) {
    let restArgs = Array.prototype.slice.call(arguments, 1);

    // 创建一个对象
    let instance = {};
    instance.__proto__ = constructor.prototype;

    // 将构造函数的作用域赋给新对象(因此this就指向了这个新对象) && 执行构造函数中的代码(为这个新对象添加属性)
    constructor.apply(instance, restArgs);

    // 返回新对象
    return instance;
}
console.log(myNew(Person, 'Alex', 22)) // {name: 'Alex', age: 22}

然后我们再稍加整理一下, 完整的代码如下

let myNew = function (constructor) {
    if (!(constructor instanceof Function)) { throw new Error('arguments[0] require a function')};
    let restArgs = Array.prototype.slice.call(arguments, 1);

    // 创建一个对象
    let instance = {};
    instance.__proto__ = constructor.prototype;

    // 将构造函数的作用域赋给新对象(因此this就指向了这个新对象) && 执行构造函数中的代码(为这个新对象添加属性)
    let result = constructor.apply(instance, restArgs);

    // 返回新对象
    return result instanceof Object ? result : instance;
}
console.log(myNew(Person, 'Alex', 22)) // {name: 'Alex', age: 22}

至此,我们就完全模拟了new创建的整个过程!过程中有错误的地方,欢迎评论留言!