分析构造函数new的过程中发生了什么、手写实现new功能

248 阅读2分钟

引用MDN的一句介绍:new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

乍一听有点懵,可以先看看他是怎么用的。

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function() {
    console.log(`Hi,帅哥,我是${this.name}。`)
}

let dajiao = new Person('大脚', 40)
console.log(dajiao.name)	// '大脚'
console.log(dajiao.age)		// 40
dajiao.sayHi()			// Hi,帅哥,我是大脚。

通过上面的代码可以知道:

  • 构造函数的实例对象可以访问构造函数Person.prototype里面的属性。
  • 实例对象就是构造函数里面的this。 因为new是一个关键字,我们无法直接重写或者覆盖💢,但是可以封装一个函数,接收构造函数和其他参数,类似于下面的用法。
function objFactory(){

}
function Person(name, age) {
    this.name = name
    this.age = age
}
let dajiao = objFactory(Person, 'dajiao', 40)
第一版

基于上面几点,尝试着写出第一版

function objFactory(){
    let obj = new Object()
    let args = Array.from(arguments)
    let Constructor = args.shift()
    obj.__proto__ = Constructor.prototype
    Constructor.apply(obj, args)
    return obj
}
function Person(name, age) {
    this.name = name
    this.age = age
}
let dajiao = objFactory(Person, 'dajiao', 40)
console.log(dajiao)

(●ˇ∀ˇ●)

上面的代码,我们实现了一个简单的new的功能,具体步骤如下:

  1. 创建一个空对象obj作为要返回的实例对象
  2. 将arguments装换为数组
  3. Array.prototype.shift()删除数组第一个元素(构造函数),返回被删除的元素,赋值给Constructor
  4. 改变实例obj的__proto__指向为构造函数的prototype,这样就能保证obj可以访问构造函数原型属性
  5. 把Constructor当做普通函数来执行,并改变this指向为obj,传入其他参数
  6. 返回实例对象obj
第二版

一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤。 我们只需要稍微修改一下代码就可以做到这个功能,获取到后早函数的返回值,如果是对象就返回此对象,否则返回obj

function objFactory(){
    let obj = new Object()
    let args = Array.from(arguments)
    let Constructor = args.shift()
    obj.__proto__ = Constructor.prototype
    let res = Constructor.apply(obj, args)	// 获取返回值
    return (res && typeof res === 'object') ? res : obj		//判断是对象的话返回此对象,否则返回原来的obj
}