前言

我们在项目的开发、面试的过程中,我们都在接触原型原型链,这里这样说大家没有印象。各位朋友在vue 2中做全局挂载,这个做过吧。

1
Vue.protype.$xx = xx

上面这个代码是不是很熟悉?没错,上面的代码就是我们在vue 2中做全局挂载的一种方式。这个就是一个原型的操作。那这个也只是一个原型操作啊,那什么是原型链,你还是没说啊,ei,让我们往看下面。一起来揭开原型链的神秘面纱。

什么是原型链(Prototype chain)?

什么是原型链?我们谈原型链的时候,继承它就跑不脱(跑不掉),它跟原型链是挂钩的,二者并存的一个关系。

我们看一下MDN的官方解释。

每个实例对象(object)都有一个私有属性(称之为 __proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。并作为这个原型链中的最后一个环节。

大白话就是说我们每一个对象都有一个私有属性__proto__,层层向上的一个__proto__的一个链就是原型链

我们看张图。

image.png

我们再画张图看一下他们的存在关系。

未命名.jpg

上图中的__proto__的一个继承过程,形成了一个链,而这个链就是常说的原型链

那什么又是继承呢?我们看下面。

什么是继承(inherit)?

继承这个很好解释,我们这里就不用MDN的官方解释了。直接上大白话解释。张三将ta的东西传承给ta的儿子张四,这样的一个过程就叫做继承

让我们开始来看代码案例

让我们一起来看代码案例,相信朋友你看完你就懂了,他人再问不再百度/沉思。

object对象的原型及原型链

我们先来创建一个对象(Object),看看它自身的原型原型链

1
2
3
4

const obj = {}
console.log(obj);

image.png

我们发现它的原型上有一个__proto__null,这一个继承自Object,再加上它自己又一个__proto__,所以它的原型链有两层,由此我们得到以下结论。

  • obj原型链有两层。
  • Object原型链上的顶层。

构造函数class类的原型是不是也是这样呢?下面让我们一起来看看。

构造函数class类的原型

构造函数

我们先一起来看构造函数中的原型原型链

1
2
3
4

function Prototype () {}
console.log( new Prototype());

image.png

我们根据我们的打印结果和上面的图片发现构造函数它有三层,这是为啥呢?

解答:

因为构造函数它本身就存在一个原型实例,然后我们new进行实例的时候,我们得到的是这个被实例化的构造函数,它又继承了构造函数原型,所以它就存在三层。我们看一下未被实例构造函数

1
2
3
4

function Prototype () {}
console.log(Prototype.prototype);

image.png

我们发现我们打印出来它的原型上,就只存在两层。这就验证了我们刚刚说的话。

class

我们看看class类中的原型链长啥样。

未被new实例化

1
2
3
4

class Prototype {}
console.log( Prototype.prototype );

image.png

new实例化后的

1
2
3
4

class Prototype {}
console.log( new Prototype () );

image.png

我们发现class构造函数在未实例化和实例化都是一样的。它俩明明都不是同一个东西,为啥为是一样的呢?

我们想要搞清楚这个问题,我们就需要知道class是这个啥,为啥原型链都是一样的。

class解读:

classES2015/ES6中新增的语法糖,其本质上也是一个构造函数,只是方便我们用class关键字进行使用罢了。

原型链深入了解及进阶

下面我们将进行实战使用。系好安全带开始发车了。

案例一:

1
2
3
4
5
6
7
8
9

const info = {}
info.__proto__.name = '张三'

const obj = {}
obj.__proto__ = info.prototype

console.log(obj.name);

问:打印出来的obj.name是什么?

答案:’张三’

解读:

这里我们在__proto__的上添加了一个name的键值对,然后我们修改了obj的原型为info的原型。
我们并没有在info中创建name的键值对,然后obj在它自身没有找不到,它就去它的上层原型上进行查找,找到了就给我们返回出来,如果没找到就是undefined。我们可以验证一下。

1
2
3
4
5
6
7
8
9

const info = {}
info.__proto__.name = '张三'

const obj = {}
obj.__proto__ = info.prototype

console.log(obj.age);

image.png

案例二:

1
2
3
4
5
6

const obj = {}
obj.__proto__ = 1

console.log(obj);

问:obj__proto__是啥?

答案:Object

❓❓❓我们刚刚不是对它的__proto__进行更改了吗。它为啥还是Object,你肯定在骗我,我们看图。

image.png

解答:

__proto__不允许修改为非对象(Object)。在我们修改的时候就给我们拦截了,所以我们根本就没有修改到。

案例三

1
2
3
4
5
6

const obj = {}
obj.__proto__ = null

console.log(obj);

问:obj__proto__是啥?

答案:No properties

❓❓❓我们刚刚不是对它的__proto__进行更改为null吗。它为啥是No properties,你肯定在骗我,我们看图。

image.png

解答:

null的类型为object,所以它是可以修改成功的。以至于null的类型为啥是object,这则是因为js遗存下来的一个bug,而null本身是啥也没有的,所以它就会变成图上的那样。

案例四:

1
2
3
4
5
6
7
8
9

const info = {}
info.__proto__.name = '李四'

const obj = {}
obj.__proto__ = info

console.log(obj);

问:打印出来的obj.name是什么?

答案:’李四’

解读:

这里的原理和案例一是一样的,我们的infoObject类型,所以可以修改成功。

image.png

原型链的副作用

在我们操作原型原型链的过程中,如果原型上不存在我们需要的属性,就会一直在原型链上查找该属性,这是一个比较耗时过程,直至找到或者找不到才会结束查找。

总结

在我们更深一步了解javascript的过程中,了解及掌握原型原型链是一步必走的路。说句题外话,这东西有时候真的好绕😭。

本篇文章存在错误或不足之处,还请各位朋友指出并进行评论留言。