画一张属于自己的JS原型链

1,414 阅读5分钟

简单明了 一图流 image.png

虽然看起来乱糟糟的,但其实顺着图中的关系一步步梳理起来其实真的是简单明了,实在看着费劲没关系画图水平有限,咱们一步一步来把这条原型链撕个细碎。  

先明确几个概念:

原型

当对构造函数使用new语法实例化一个对象后,该对象下面有一个__proto__属性,该属性指向会指向该构造函数的prototype(原型对象),原型对象会自动获得一个constructor属性,指向对象的构造函数,同时原型对象可以给该构造函数产生的实例共享属性和方法。  

举个例子

WechatIMG259.png 只截取了一部分图,可以看出,实例化了一个Array对象,该对象下的__proto__指向了Array构造函数的原型对象,而该原型对象中的constructor属性又指向了Array构造函数,同时该原型对象给实例化的对象共享了如every()等诸多方法。  

所有引用类型(除了Null以外)都存在一个__proto__属性,该属性指向它的构造函数函数的prototype原型对象,该原型对象中的方法和属性是可以被继承共享的。  

再来看个例子

   function Person (name) {
       this.name = name;
   }
   
   let person1 = new Person('小明')
   let person2 = new Person('小红')

这有一个人类的构造函数,通过new实例化出person1小明,person2小红两个人,如何才能让他们两个都具备跑的方法呢?给person1和person2分别写一个?那如果实例化100个呢?给100个都分别写一个方法?

所以最简单的方法就是在Person的原型上添加run方法,这样所有的实例化对象都可以共享原型中的方法

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

   Person.prototype.run = function () {
       console.log(this.name + '会跑')
   }

   let person1 = new Person('小明')
   let person2 = new Person('小红')
   person1.run() // 小明会跑
   person2.run() // 小红会跑

原型链

上面的例子,实例化后的person1,person2对象中并没有run方法,依托原型链,当查找特定属性或方法时,先去这个对象里去找,如果没有的话就去它的原型对象里面去找,如果还是没有的话再去向原型对象的原型对象里去寻找,如果依旧没有就接着一层一层向上寻找,这就形成一条链条,也就是我们所说的原型链,而__proto__是原型链查询中实际用到的,它指向构造函数的原型对象。

剖析

弄懂原型和原型链以后,我们一步一步剖析顶部的大图。  

首先,实例化一个Array对象new Array(), 通过__proto__可以得到该对象的原型对象,原型对象通过constructor可以得到实例化对象本身的构造函数,而构造函数本身又可以通过prototype拿到原型对象

let arr = new Array()
// 通过__proto__可以拿到Array的原型对象
console.log(arr.__proto__ === Array.prototype) // true
// 通过原型对象的constructor可以拿到构造函数本身
console.log(arr.__proto__.constructor === Array) // true

做出图来就是这个样子: Array构造函数通过new实例化出array对象,array对象的__proto__指向Array的原型对象,Array原型对象的constructor属性可以得到实例化对象本身的构造函数Array,Array构造函数的prototype又指向该构造函数的原型对象

image.png

接下来,Array构造函数是怎么产生的呢?函数本身也是一种对象,所以按照上面的方法,可以通过__proto__拿到原型对象,再通过constructor拿到对象本身的构造函数,所以: image.png 也就可以知道,Array.proto.指向Function原型对象,Function原型对象的constructor指向Function构造函数,所以Array构造函数是由Function构造函数实例化来的 image.png 继续拆解,Function构造函数又是怎么产生的呢?老办法,通过__proto__拿到原型对象,再通过constructor拿到构造函数 image.png

发现Function构造函数是由Function构造函数本身实例化而来的,到此就可以清楚地看到构造函数这一边,Function构造函数已经到达顶端了,用大白话讲,所有构造函数的顶头上司都是Function构造函数,其他构造函数都是老大Function实例化出来的。 image.png 然后看看原型对象这一边,什么产生的Array原型对象?  

这里可能会有疑问,不是通过prototype拿到的原型对象么?  

对,确实可以通过prototype属性拿到原型对象,但这里要明确的是通过prototype拿到的原型对象是被谁创造出来的。  

先通过prototype拿到Array原型对象,再通__proto__拿到原型对象的原型对象,最后通过constructor拿到构造函数 image.png

可以看出,Array构造函数的原型对象是通过Object构造函数实例化出来的,所以继续画图 image.png

那Object构造函数是谁创造的呢?上面说了所有构造函数的顶头上司都是Function构造函数,那么Object构造函数就是Function构造函数实例化出来的 image.png

如果Object构造函数是被Function构造函数实例化出来的话,那么Object.__proto__ === Function.prototype也是成立的 image.png

那Function构造函数的原型对象是谁产生的呢?Object原型对象又是谁产生的呢?看一下: image.png image.png 可以看出,Function构造函数的原型对象是由Object构造函数实例化出来的,而最下面报错"cannot read property constructor of null"说明null上没有constructor,也就是Object.prototype.__proto__ === null image.png

补全关系图 image.png

注意

至此整个原型链关系图都画完了,可以看出任何函数继承(__proto__)自Function.prototype,任何对象最终继承(__proto__)自 Object.prototype  

另外,__proto__这个属性切记不要在实际生产中使用,它不推荐被使用,不要写Array.prototype. __proto__.constructor,直接写Array.prototype.constructor