从零开始构建 new:模拟 JavaScript 内置对象创建过程,征服技术面试

187 阅读4分钟

引言:

学会手写 new 运算符的模拟实现,不仅可以深入理解 JavaScript 中对象创建和构造函数的工作原理,并且在面试中,面试官可能会要求我们解释或实现一个简化版的 new 操作符,以评估我们对 JavaScript 的核心概念的理解程度。那么就跟随本文来手写一个简化的new 操作符。

分析:

我们在实例化一个对象时,new在内部到底承担了什么作用,我们不妨来看看代码

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

// 在Person的原型上添加方法getName来获取name属性
Person.prototype.getName = function(){
    return this.name;
}

const awei = new Person('awei', 20)

在上述代码中我们使用 new 来实例化一个对象,而其在内部执行了一系列的操作。

  1. 创建新对象new 操作符首先会创建一个新的空对象 {}
  2. 设置原型链:新创建的对象会被链接到构造函数的 prototype 属性,即新对象的内部属性 [[Prototype]](可通过 __proto__ 访问)会被设置为构造函数的 prototype 对象。
  3. 绑定 this:构造函数内部的 this 关键字被绑定到这个新创建的对象上。

了解这些操作后,我们就基于上述代码来手写一个new,来帮助我们更好的理解new

实战:

  • 首先创建一个构造函数作为初始化新对象的模板。
function Person(name, age) {
    this.name = name;
    this.age = age;
}
  • 此时的函数 Person的原型上是没有方法的,我们可以通过打印Person.prototype来查看。
    console.log(Person.prototype); 
    // Person.prototype = { constructor: [Function: Person] }
    
  • 那我们不妨添加一个方法进去,再打印Person.prototype查看就会添加我们的新方法。
    Person.prototype.getName = function () {
        return this.name;
    }
    console.log(Person.prototype); 
    // Person.prototype = { constructor: [Function: Person] , getName: [Function (anonymous)] }
    

arguments 是一个类数组对象,包含了传递给函数的所有参数。

  • 在这里我们创建一个objectFactory()函数来模拟new的作用,并且我们需要依次传入参数Personnameage。因此函数的arguments{ '0': [Function: Person], '1': name, '2': age },我们也可以打印查看一下。

    function objectFactory() {
        console.log(arguments);
    }
    
    let awei = objectFactory(Person, 'awei', 20)
    

    我们可以得到这样的打印结果: image.png

  • 在创建函数后我们就需要为其中添加点"内饰"了,首先new运算符在执行的时候会创建一个空对象,并且这个 空对象 与 构造函数 没有任何“血缘关系”,并不像 java 那样的父子关系什么的,所以我们在函数内部也需要创建一个空对象。

    const obj = new Object(); // 空对象创建
    

    然后我们需要将空对象的原型指向构造函数,在我们传入的参数内就包含构造函数,所以我们需要在arguments内将其取出,而在这里我们就可以开始“炫技”了

    // 常规方法: 
    const Constructor = arguments[0];
    // 炫技:
    const Constructor = [].shift.call(arguments); // 取出第一个参数,相当于arguments[0]
    
    // 打印查看一下
    console.log(Constructor);
    

    我们可以得到这样的结果,证明现在Constructor = Person image.png 当面试官看到你用常规方法时可能就已经开始打瞌睡了,但是当我们拿出[].shift.call(arguments)这一套组合拳下来,面试官就会欣慰的笑笑了。

    在这里shift是数组的一个方法,可以返回并移除数组的开头,但是由于arguments是类数组,其并不包含这个方法,所以这里我们通过call() 来将 shift 内部的 this 指向了 arguments,这样就使arguments也可以使用shift方法了,并且我们巧妙的使用一个[].shift创建了一个对数组原型方法 shift 的引用,只能用完美来形容😎。

  • 接下来就是将obj的原型指向Constructor(构造函数) 的原型,并且绑定this

    // 将 obj 的原型指向 Constructor(构造函数) 的原型
    obj.__proto__ = Constructor.prototype;
    
    Constructor.apply(obj, arguments); // 将 obj 作为 this 指向,执行构造函数
    // 并且由于上述进行了[].shift操作,将argument[0] = Person 移除了,剩下的就是给 obj 添加属性和方法,并且apply第二个参数是可以传入数组的
    
    // 打印查看一下效果
    console.log(obj);
    

    得到这样的结果: image.png

  • 最后再返还obj就完成了

    return obj;
    

实例:

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

Person.prototype.getName = function () {
    return this.name;
}

function objectFactory() {
    const obj = new Object(); 
    const Constructor = [].shift.call(arguments); 

    obj.__proto__ = Constructor.prototype;

    Constructor.apply(obj, arguments);
    
    return obj;
}

let awei = objectFactory(Person, 'awei', 20)

---欢迎各位点赞、收藏、关注,如果觉得有收获或者需要改进的地方,希望评论在下方,不定期更新

0bae-hcffhsw0416753.gif