记录一下复习js模拟new一个实例的过程,顺带复习一下原型链。

60 阅读4分钟

记录一下复习js模拟new一个实例的过程,顺带复习一下原型链。

首先梳理一下原型链,我们知道有构造函数,实例,原型对象

构造函数通过new出一个实例来,new Person() = person, Person就是构造函数,person则是实例

//一个构造函数 Person
function Person(name, age){
   this.name = name
   this.age = age
}
Person.prototype.getName = function(){
    console.log('hh')
}
//通过new得到实例
const instance = new Person('hh', 18)

可以知道,实例与构造函数的关系 构造函数 ——new——> 实例

实例对象中有一个__proto__属性可以访问到这个实例的原型,这样就是为什么我们能通过实例调用原型上面的方法,因为实例在调用方法时,会先从自己这块对象中查找有没有这个方法,如果没有则会通过__proto__找到自己的原型,然后查看原型上有没有这个方法。

instance.getName()
//此时就是通过__proto__找到了原型对象,原型对象上有一个方法叫做getName。 此时会打印 'hh'

所以得知,实例与原型的关系大概是 实例 —— __proto__——> 原型

那我们通过原型,可以不可以找到最开始的构造函数呢? 是可以的

// Person.prototype 可以让构造函数拿到自己的原型
const prototype111111 = Person.prototype
// 原型上的constructor则是可以访问到自己的构造函数
prototype111111.constructor === Person

所以原型与构造函数的关系 原型 ——constructor——> 构造函数 到这个时候可以发现, 这其实可以串联起来

构造函数 ——new——> 实例 —— __proto__——> 原型 ——constructor——> 构造函数

image.png

构造函数也可以通过自己的prototype访问到自己的原型。 完整的原型链就出来了。

image.png

现在要做的就是模拟创建一个实例, 因为是需要模拟new嘛。 没有了new字符,我们需要自己创建一个对象。 let obj = {}就行了 但是这个obj需要能访问到我们的原型,这样才能保证这个原型图各个环节不会出问题,能正常访问 那正常想法就是 直接把 obj.__proto__ = 原型 直接做一个赋值操作就好了, 但是__proto__不是一个标准属性,所以还不可以这么做 可以用Object.create()方法创建一个对象,Object.create()方法的第一个参数接受一个对象, 他将以此对象作为自己的原型对象,创建一个对象并返回。

const obj = Object.create(`原型对象`)
// 此时的obj就是一个新对象,并且可以通过 obj.__proto__ 访问到自己的原型对象了

这个时候原型链已经完整了,但是在new的时候还会给实例进行赋值属性的操作。现在还没有赋值。 我们的最终目的是obj = {name: 'hh', age: 18}, 并且能正常访问原型对象和原型上的方法。 但是我们没法预知每个构造函数会接受几个参数,并且给什么属性赋值。 目前已经知道 构造函数 大概是这样

// 但是没法知道每个构造函数内部做了什么赋值动作,和接受那些参数
function Person(name, age){
   this.name = name
   this.age = age
}

// 我们知道他是给this赋值,所以这个时候有一个巧妙的方法,我们可以调用构造函数, 并且把this改成obj
Person.apply(obj, ['hh', 18])
//这个时候 obj 就已经被赋值了  
//obj = {
//   name: 'hh',
//   age: 18
//}

到这里,我们已经有了完整的原型链,并且可以得到正常带有属性的实例了。我们尝试把他组合到一个函数里面

// 首先我们需要原型对象,用来创建obj, 同时我们还需要构造函数,用来给obj赋值属性。
// 所以我们可以直接要一个构造函数,因为构造函数.prototype可以拿到原型对象,
// 另外我们在赋值obj属性的时候,还需要参数,所以调用构造函数的参数也需要传进来
function createInstance(constructor, ...args){
    // 1.先把obj创建出来,并且 能通过__proto__访问到原型
    const obj = Object.create(constructor.prototype)
    // 2.给obj赋值属性 ,我们通过调用构造函数,并且改变构造函数内部的this为obj的方式为obj赋值属性
    constructor.apply(obj, args)
    return obj
}

差不多就这样子了,还有一些特殊情况需要处理, 比如构造函数自己返回了一个对象,则我们就不能返回obj了,而是直接把构造函数自己返回的对象返回。

最后附上一个完整代码。

function createInstance(constructor, ...arg){
    // 以构造函数的原型对象 作为原型创建一个对象
    const obj = Object.create(constructor.prototype)
    // 普通调用方法调用构造函数, 并将this指向为实例对象,将参数传给构造函数, 这样obj中将得到arg的属性
    const result = constructor.apply(obj, arg)
    // 如果返回的是一个对象,则返回这个对象, 如果不是则返回 obj 这个实例
    return (typeof result === 'object' && result !== null) ? result : obj
}

// Person构造函数
function Person(name, age){
    this.name = name
    this.age = age
}
// 原型上的方法
Person.prototype.getName = function (){
    console.log(this.name)
}

// 通过createInstance方法模拟new Person
let instance = createInstance(Person, 'hcx', '18')
console.log(instance)
instance.getName()