前端面试系列-JS篇-手写 new

840 阅读6分钟

之前写有关于 前端面试系列-JS篇-this指向 的文章时,挖了一个有关于手写 new 的坑,秉承着挖坑尽快填的理念,今天就来填坑了

new 运算符我们通常用来实例化对象,在 JavaScript 中已经屡见不鲜了,但都明白它内部的原理嘛?

今天我们就一起来看看 new 运算符的原理

我们要模拟 new 运算符,首先我们要知道它都做了什么

原理

new 关键字用于创建一个新对象,并将该对象的原型指向构造函数的原型。

它的实现原理可以简单描述为以下几个步骤:

1. 创建一个新对象

创建出一个新对象,来作为第三步构造函数的函数上下文

2. 将新对象的原型指向构造函数的原型

将新对象的 proto 属性会被设置为构造函数的 prototype 属性,从而将新对象的原型指向构造函数的原型。 这样,新对象就可以继承构造函数的属性和方法,从而实现了面向对象编程中的继承机制。

3. 使用新对象作为构造函数的上下文(即 this),执行构造函数

构造函数会被执行,并将新对象作为函数上下文(即 this)传递给构造函数。在构造函数中,我们可以为新对象设置属性和方法,从而对新对象进行初始化。

4. 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象

  • 构造函数的返回值如果是基础数据类型(包括 undefinednull ),那么返回新创建的对象
  • 如果返回的是函数,数组或者对象等复杂数据类型,那么返回构造函数的对象

实现

基础版本

    function myNew(constructor, ...args) {
        //判断是否为函数
        if(typeof constructor !== 'function') {
            throw 'constructor must be a function';
        }
        // 用new Object() 的方式新建了一个对象obj
        var obj = new Object()
        // 给该对象的__proto__赋值为constructor.prototype,即设置原型链
        obj.__proto__ = fn.prototype.prototype
        // 调用构造函数,将新对象作为上下文
        const result = constructor.apply(obj, args);
        // 如果构造函数返回了一个对象,则返回该对象;否则,返回新对象
        return result instanceof Object ? result : obj;
    }

这个方法接受两个参数:构造函数和任意数量的参数。它首先使用 new Object() 方法创建一个新对象,并将该对象的 proto 属性指向构造函数的 prototype 属性。然后,它使用 apply() 方法调用构造函数,并将新对象作为上下文。如果构造函数返回了一个对象,则返回该对象;否则,返回新对象。

instanceof 是 JavaScript 中的一个运算符,用于检查一个对象是否是另一个对象的实例。它的语法如下:

其中,object 是要检查的对象,constructor 是构造函数。如果 object 是 constructor 的实例,则返回 true,否则返回 false

instanceof 运算符的工作原理是基于 JavaScript 中的原型链。在 JavaScript 中,每个对象都有一个内部属性 [[Prototype]],也称为原型。原型可以是另一个对象,如果一个对象的原型是另一个对象,那么这个对象就可以访问原型对象的属性和方法。

详情可查看 前端面试系列-JS篇--一场探寻 instanceof 奥秘的奇幻之旅

这里面还有很多种版本,但无外乎是对第一步和第二步的改写或者简写

够用版本

    function myNew(constructor, ...args) {
        //判断是否为函数
        if(typeof constructor !== 'function') {
            throw 'constructor must be a function';
        }
        // 用new Object() 的方式新建了一个对象obj
        var obj = new Object()
        // 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
        Object.setPrototypeOf(obj, constructor.prototype)
        // 调用构造函数,将新对象作为上下文
        const result = constructor.apply(obj, args);
        // 如果构造函数返回了一个对象,则返回该对象;否则,返回新对象
        return result instanceof Object ? result : obj;
    }

这里和第一个版本唯一区别就是使用setPrototypeOf__proto__ 替换掉了

__proto__ 是一个非标准的属性,它允许您访问一个对象的原型。在 ECMAScript 6 中,推荐使用 Object.getPrototypeOf 方法来访问一个对象的原型,Object.setPrototypeOf 方法来设置一个对象的原型

使用 __proto__ 属性来设置原型可能会导致一些兼容性问题,并且在一些 JavaScript 引擎中,该属性可能被禁用。

进阶版本

    function myNew(constructor, ...args) {
        //判断是否为函数
        if(typeof constructor !== 'function') {
          throw 'constructor must be a function';
        }
        // 创建一个新对象,原型指向构造函数的原型
        const obj = Object.create(constructor.prototype);
        // 调用构造函数,将新对象作为上下文
        const result = constructor.apply(obj, args);
        // 如果构造函数返回了一个对象,则返回该对象;否则,返回新对象
        return result instanceof Object ? result : obj;
    }

这里是将通过 Object.create 将创建对象和修改原型合并为一步,使用它直接就能生成一个__proto__指向构造函数的 prototype 的新对象

Object.create 是一个用于创建新对象并将其原型设置为现有对象的方法。

使用 Object.create 方法,您可以创建一个新的对象,该对象的原型指向传入的另一个对象。这样,新对象就可以继承传入对象的属性和方法。

这个方法接受两个参数:构造函数和任意数量的参数。它首先使用 Object.create() 方法创建一个新对象,并将该对象的原型指向构造函数的原型。然后,它使用 apply() 方法调用构造函数,并将新对象作为上下文。如果构造函数返回了一个对象,则返回该对象;否则,返回新对象。这个方法模拟了 JavaScriptnew 操作符的行为。

当我们使用 new 关键字创建一个对象时,JavaScript 引擎会执行以上的步骤,以创建一个新的对象,并将该对象的原型指向构造函数的 prototype 属性。这样,新对象就可以继承构造函数的属性和方法,从而实现了面向对象编程中的继承机制。

测试

这里面使用进阶版的手写 new 来进行和原生 new 运算符进行测试

image.png

扩展

    function Person(name, age) {
        this.name = name;
        this.age = age;
        console.log(this, 'this')
        return {
            name: 'Bob',
            age: 30,
        };
    }

    const person = new Person('Alice', 20);
    console.log(person);

经过上面的学习,我们可以知道 person 打印出来的是 { name: 'Bob', age: 30 } ,那此时 this指向的是谁呢?

this 指向的是 {name: 'Alice', age: 20},这是为什么呢?

在这种特殊情况下,当构造函数显式地返回一个对象时,new 运算符会返回这个显式指定的对象,而不是构造函数内部的 this 指向的对象。这意味着这个返回的对象将成为实例化对象,而构造函数中的 this 并不会指向这个实例化对象。

一般情况下,构造函数模式下的 this 指向实例化对象。但是当构造函数显式地返回一个对象时,此时构造函数中的 this 不会指向实例化对象。这种情况并不常见,但的确需要注意。

我们总说构造函数模式下 this 指向实例化对象,但实际上指向的是 new 运算符内部新创建的对象,而不是实例化之后的对象

结束

在这篇文章中,我们理解了 new 运算符的原理,也手写了一个 new运算符

希望能够帮助大家更好地理解 new 运算符原理,理解 JavaScript

祝大家变得更强!