手写new

220 阅读3分钟

前言:

手写一个 new 我们需要了解原型的概念,和this指向的问题。

在执行 new 的时候做了什么

我们执行 new 来看一下 new 的过程中发生了什么:

function Person(name) {
  this.name = name
  this.sayName = function () {
    console.log('name', this.name)
  }
}
const person = new Person('ayetongzhi')
console.log('person', person)
person.sayName()

image.png

打印结果如上,根据打印结果我们可以知道:

    1. 返回了一个对象(person),叫做实例对象,这个对象不会凭空的产生,因此在 new 的过程中是创建了一个对象并返回。
    1. 将 this 指向创建的新对象,并执行函数。需要将 this 指向新的对象,才能构造函数的属性赋值给实例对象。
    1. 改变实例 的 __proto__  属性,指向构造函数的原型。如下图所示,通过构造函数  (Function)  创建的实例 (instance)  ,它的 __proto__  属性指向构造函数  (Function) 的 prototype
  • 4.返回一个新对象 image.png 构造函数显示的返回一个对象,则返回该对象。下面对这条问题进行解释,上代码:
function Person(name) {
  this.name = name;
  this.sayName = function () {
    console.log("name", this.name);
  };
  // 显示的返回一个对象
  return {
    name: "maomao",
    sayName: function () {
      console.log("name", this.name);
    }
  };
}
const person = new Person("ayetongzhi");
console.log("person", person);
person.sayName();  //maomao

运行结果可知,在 new Person 操作后返回了return后面返回的对象。

image.png

实现 new

根据 new 操作执行的内容,来一步步实现手写的 new

    // fn 构造函数
    // ...args 构造函数传入的参数
        function myNew(fn,...arg){
          // 创建一个新对象
          const obj = {}

          // 将新对象的原型指向构造函数的原型
          obj__proto__ = fn.prototype

          // 改变this,执行构造函数,并将新对象作为this的上下文
          const result = fn.apply(obj,arg)

          // 如果构造函数返回一个对象,则返回该对象,否则返回新对象
          return typeof result === 'object' ? result :obj
          //或者
          //return result instanceof Object ? result : obj
        }
    

注意:

通过以上代码我们实现了手写的new,然而 __proto__  属性是一个有争议不建议使用的属性,要看浏览器是否兼容, 建议使用Object.create()方法来代替。

Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。举个例子:

    let a = {
      name: 'maomao'
    }
    let b = Object.create(a)
    console.log('b 的 __proto__ 指向 a', b.__proto__ === a) // true

以上的代码 let b = Object.create(a)  使新创建的对象 b 的 __proto__  指向了 现有对象a

因此,我们优化一下手写的 myNew 方法:

    // fn 构造函数
    // ...args 构造函数传入的参数
    function myNew (fn, ...args) {
      // 创建新对象 obj,并改变 obj 的 __proto__ 指向构造函数的 prototype
      const obj = Object.create(fn.prototype)

      // 执行构造函数,并将新对象作为this的上下文
      const result = fn.apply(obj,arg)

      // 如果构造函数返回一个对象,则返回该对象,否则返回新对象
      return typeof result === 'object' ? result :obj
      //或者
      //return result instanceof Object ? result : obj
    }
    

运行以下代码检验手写的 new 方法是否存在问题:

function myNew(fn, ...arg) {

        const obj = Object.create(fn.prototype)

        // 执行构造函数,并将新对象作为this的上下文
        const result = fn.apply(obj, arg)

        // 如果构造函数返回一个对象,则返回该对象,否则返回新对象
        return typeof result === 'object' ? result : obj
      }
      function Person(name) {
        this.name = name;
        this.sayName = function () {
          console.log("name", this.name);
        };
      }
      const person = myNew(Person, "ayetongzhi");
      console.log("person", person);
      person.sayName();  // ayetongzhi

当没有显示的返回对象的时候,新创建了对象。并改变this指向到新的对象,同时执行构造函数。改变了新创建对象的 __proto__  指向了构造函数的 prototype,与 new 操作符执行的结果一致。。

image.png

当构造函数显示的返回一个对象的时候,打印结果如下,与 new 操作符执行的结果一致。

    function myNew(fn, ...arg) {

      const obj = Object.create(fn.prototype)

      // 执行构造函数,并将新对象作为this的上下文
      const result = fn.apply(obj, arg)

      // 如果构造函数返回一个对象,则返回该对象,否则返回新对象
      return typeof result === 'object' ? result : obj
    }
    function Person(name) {
      this.name = name;
      this.sayName = function () {
        console.log("name", this.name);
      };
      return {
        name: "maomao",
        sayName: function () {
          console.log("name", this.name);
        }
      };
    }
    const person = myNew(Person, "ayetongzhi");
    console.log("person", person);
    person.sayName();  // maomao

image.png