手写代码 : 根据javaScript中new 的原理 , 实现一个 new 函数

595 阅读4分钟

前言

大家好 , 我是一名在校大二学生 ~

今天考完六级后 , 决定深入搞搞 new 操作符, 并使用函数实现一遍 new .

请倔友往下看 ~

明白 new 的底层


首先要明白 , new 的三个问题

  • new 是什么 ?
  • new 作用于谁 ?
  • new 之后 , 底层发生了什么 ?

只有搞懂了这些 , 我们才能自主实现一个和 new 功能一样的函数 ,


我总结出下面这段话 , 回答前两个问题 :

new 是 javaScript 的操作符 , 通过作用于某个构造函数 , 创建该构造函数的实例对象

有两个关注点

  • new 是作用于构造函数的
  • 对构造函数使用后 , 返回该构造函数的实例对象

请倔友看下面这句代码

const p = new Person(name,age)

这里的 Person() 是构造函数 , 所以 new 才能作用于它 , new 之后返回 Person() 构造函数的实例对象 , 这里使用 p 变量来引用

我们补全 构造函数的代码

首先使用适合 java 选手体质的 es6 写法 , 完整代码如下 :

class Person {
    construct(name ,age){
      this.name = name ;
      this.age = age ;
    }
     speak(){
          console.log(`我的名字叫${this.name},今年${this.age}岁`)
      }
  }
const name = 'ganzhibin';
const age = 19 ;
const p = new Person(name,age)

再使用便于考古的 es5 写法 :

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.speak = function () {
  console.log(`我的名字叫${this.name},今年${this.age}岁`)
}
const name = 'ganzhibin';
const age = 19 ;
const p = new Person(name,age)

在 es5 中 ,我们发现 : 方法需要手动添加到构造函数的 prototype 对象上

其实 , es6 的构造函数方法是 es5 的语法糖 , 自动将方法添加到类的原型对象上,因此可以被所有实例共享 。

说实话 ,这颗糖真甜 ~ 🤡

现在只剩下最后一个问题了


new 之后 , 底层发生了什么 ?

下面是一段使用 new 关键字的代码示例:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayName = function () {
  console.log(this.name);
}
const p1 = new Person('ganzhibin', 20)

在这个例子中,我们创建了一个构造函数Person,当我们调用 new Person('ganzhibin', 20) 时,发生了以下步骤:

  1. 创建新对象:JavaScript 引擎首先创建了一个全新的空对象 {}
  2. 设置原型链:接着,该新对象的内部属性 [[Prototype]](可通过 __proto__ 访问)被设置为 Person.prototype,建立了新对象与构造函数之间的原型链连接。
  3. 绑定 this:然后,构造函数内的 this 被绑定到了新创建的对象上,使得我们可以在构造函数内部对新对象进行属性和方法的添加。
  4. 返回新对象:最后,如果构造函数没有显式返回另一个对象,则默认返回新创建的对象。

手写 new 函数

经过上面的铺垫 ,要实现一个 new 函数

首选确定函数的参数是什么 ?

const p = new Person(name,age)

仔细看上面的代码 , 我们发现 , 需要一个构造函数作为参数 , 还需要初始化对象的参数 , 而这个参数是可变的 , 上面只是初始化了 name , age , 可能将来还要实现初始化 gender

所以可以这样写 :

function myNew(constructor , ...args){
  //constructor 构造函数
  // ... args args为可变参数
}

而在使用 new 操作符后 , 底层的机制是 :

  1. 创建新对象:JavaScript 引擎首先创建了一个全新的空对象 {}
  2. 设置原型链:接着,该新对象的内部属性 [[Prototype]](可通过 __proto__ 访问)被设置为 Person.prototype,建立了新对象与构造函数之间的原型链连接。
  3. 绑定 this:然后,构造函数内的 this 被绑定到了新创建的对象上,使得我们可以在构造函数内部对新对象进行属性和方法的添加。
  4. 返回新对象:最后,如果构造函数没有显式返回另一个对象,则默认返回新创建的对象。

所以我们按照 new 的底层开始写代码 :

function myNew(constructor , ...args) {
    // 1. 创建一个简单的js 空对象
    const obj = {};
    // 2. 将空对象的原型指向构造函数的原型
    obj.__proto__ = constructor.prototype;
    //或者这样写 : Object.setPrototypeOf(obj, constructor.prototype)
    // 3. 将这个[空对象]作为this的[上下文执行构造函数] , 即this 指向新创建的空对象
    const result = constructor.apply(obj, args)
    // 4. 如果构造函数返回一个对象 , 则返回该对象 ; 否则返回这个新创建的对象
    return result != null && (typeof result === 'object' || typeof result === 'function') ? result : obj;
    //以上return注意 : typeof null === 'object' , 所以要判断
    //typeof result === 'object' || typeof result === 'function'
    //构造函数可能会出现返回一个函数
}

测试

// 5. 测试

//构造函数

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


const person = myNew(Person, "ganzhibin", 19);
console.log(person) // { name: 'ganzhibin', age: 19 }
console.log(person instanceof Person) // true

使用 new 的话也是这个结果

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

const person = new Person( "ganzhibin", 19);
console.log(person) // { name: 'ganzhibin', age: 19 }
console.log(person instanceof Person) // true

总结

重点明白以下问题 , 实现 new 函数代码就可以用手了 ~ 🤡

  • new 是什么 ?
  • new 作用于谁 ?
  • new 之后 , 底层发生了什么 ?

祝我们每天都充满 Passion , 欢迎大家 , 一起交流 ~