天天看点

JavaScript中原型链的个人浅薄理解

以下内容都是基于读者有了一定的原型链理解基础上写的,仅仅只是浅薄的理解,如有错误欢迎指出。

首先需要明白这么一点,也是理解原型链的基础:原型链的作用是什么?

为了回答这个问题,要引入以下情景(或许你看过,但这个例子我认为是最合适作为理解原型链的作用的一个):

function Cat(name){  // 定义一个动物的构造函数
    this.name = name;
    this.voice = function () {
    	console.log('喵喵');
  };
}

let cat1 = new Cat('mimi');  // 
let cat2 = new Cat('kiki'); 
           

以上代码中,我通过

Cat

构造函数,定义了两个对象:

cat1

cat2

,他们都有对应的名字和方法,但是存在一个问题,cat1和cat2中都会存在一个 voice 方法,直观上看他们是一样的,但是实际上这俩方法并不相等:

这表示通过构造函数生成对象的时候,内部的变量和方法都会重新生成,且需要重新开辟内存空间,这就导致了性能上和空间上的浪费:因为对于每一个猫猫来讲,他们都会发出叫声,所以这个方法应该是

Cat

构造函数生成的

cat

对象之间

共享

的。

所以为了解决这个性能、空间上会浪费的问题,引入了

原型链

的概念。

在讲我个人的理解之前,首先要讲下上面那个理解如何让voice在对象之间共享:

Cat.prototype.voice = function () {
    console.log('喵喵');
};

cat1.voice();  // 发出喵叫
cat2.voice();
console.log(cat1.voice === cat2.voice); // true
           
直接在

Cat

构造函数的

prototype

对象

上定义

voice

方法即可。

这么直接写一段抽象的话可能很难理解,但是不要害怕,往下看。

我以我个人的理解来阐述下我是如何去理解原型链这个概念的。

我默认大家对

__proto__、prototype

等属性已经有了一个大致或者全面的了解。不了解的话可以先去了解下,知道他们是干嘛,有啥关系就行。

原型链

的理解,我着重在

字,想象一串链条,一条一条链条结组成长长的链,可以从末端

顺着链条

爬上顶端,有

开始

也有

结束

,他们之间通过

链条结

之间一个一个互相连接起来

JavaScript中原型链的个人浅薄理解

每一个由构造函数生成的对象都会有一个

__proto__

属性,该属性只存在对象中,而在构造函数中含有

prototype

属性。比如上面的例子中,

cat1

就有:

cat1.__proto__

,而Cat有:

Cat.prototype

二者是严格相等的!

于是就可以这么说:

cat1的原型对象是Cat

。可能有的小伙伴对原型对象中这个原型二字感到很抽象,很陌生,或者说很难理解,那我换一个翻译:

雏形。

JavaScript中原型链的个人浅薄理解

雏形

是啥?CPU的原材料是沙子,或者说雏形是一堆沙子;借用西游记里孙悟空的一句话:妖怪,我要把你打回原形!比如是对一个由蛇精化作的美女而言,孙悟空一棒下去,直接把美女打成一条蛇了,为啥?因为本质没改变,外表是美女,本质是一条蛇,她的雏形(原型)就是一条蛇。

回到上面那个例子,

cat1

的原型(雏形)是啥?

那肯定就是

Cat

了,因为

cat1

是由

Cat

“变”(用变字可能不是很准确,但你能理解我的意思,对吧?)来的,

cat1

再怎么改变,都无法脱离它的本质就是

Cat

,换句话说,原型就是

Cat

理解了上述内容,你可能就有疑问了,别的博客内写的:通过原型链(

到现在为止,你都可以在大脑内想象一条链条存在,想象不出来看我上面贴的图

),可以让对象访问到原型对象上的各种方法,甚至原型对象的原型对象上的方法(是不是比较绕?)。

其实很好理解:一条蛇化作了人,蛇有七情六欲,蛇会吃喝拉撒(

神话意义上的蛇,不是动物世界的那种

),那化作了人之后,不可能自己原来会做的事情,化成人了就不会做了吧?照样会吃,会动情,动怒。对应到Cat那个例子中去,就是:

Cat

会做的,

cat1

照样会做。

Cat

在其

prototype

上定义的

voice

cat1

cat2

就能直接使用,为啥?因为他俩的本质(雏形,原型)就是

Cat

Cat

会的他俩自己也会。

是不是还是比较难理解?那我换个说法:

Cat一出生就会的东西,它化成了其他样子,难道就不会了嘛?(抽象意义上的出生)

婴儿刚出生时就会的呼吸,长大成人了就不会了嘛?

例子可能不是好例子,但是这么理解我觉得是可以的。

你到目前为止可能一直有个疑问:是怎么会的?cat1是怎么会知道Cat有 voice 这个方法的?等等一堆疑问,解决这些疑问的唯一关键字就是:

__proto__

没错,对象和它的原型对象(雏形对象,本质对象)之间就是通过

__proto__

起来的!

注意这个

字!

正如链条的链接需要通过一个一个链条结:

JavaScript中原型链的个人浅薄理解
对象和原型对象(雏形,最初的那个对象)之间也是需要这种类似“

”的东西,来让它们之间链接起来,形成一个整体,一条完整的链条。这个“

”就是:

__proto__

就是这个小东西,让对象和原型对象之间有了连通的

通道

,有了从链条底端,向链条开端爬去的

利器

。通过它,对象就能获取到原型对象上定义的方法(

也就是定义在 prototype 对象上的方法

,实际上在前面,我通过一段代码展示了对象的 __proto__属性是严格等于构造函数上的 prototype,就可以将二者看成是同一个东西,但我怕太抽象,所以就通过简单的例子来讲述这点。)。

你可以想象成在底下链条底下有只蚂蚁向上爬,在链条中的某个结点上有一小撮蜂蜜,蚂蚁爬上第一个结点,发现没有,继续向上爬,直到找到那个蜂蜜。

这里Js解释器就是通过

__proto__

属性一个一个向上去寻找,在cat1对象本身内,没有定义voice,那么就通过

__proto__

,发现它与Cat相等,就在

Cat

内寻找,发现有

voice

这个方法,于是就调用了。
很明显,小伙伴肯定也会意识到一个问题,链条有底端,也有开端,那么在

原型链

概念中,是否也存在呢?

答案是显然的,有相关知识的小伙伴就会明白原型链的最顶端是

null

,正如链条的开端上面是

空气

一样。

借由前一个蛇精的例子,蛇是卵生,最开始就是一个卵。

所以有这么一个对应关系:人<----蛇精<-----蛇蛋。

蛋怎么来的?那就当做是空气中突然冒出的吧(

null

)。

前面的所有文字中,我刻意省略了Object这个东西,相信看过不少博客的小伙伴记都记住了这样一个关系(大致模样):

obj <----> Funciont <----> Object <----> null

原型链的基础是从

null

开始,慢慢延伸出

Object

,再是

Funcion

,再是一个具体的对象(

obj

)。

它们之间怎么链接起来的?

答:通过

__proto__

好了,以上就是我思考原型链的全过程。

现在回过头去看看原型链,或者说想一想原型链,你有没有理解到?

问问自己原型(prototype)翻译成

雏形

会不会更好理解一点,原型链是为了解决什么问题而造出来的,对象和原型对象之间通过是什么链接起来,二者有啥对等关系没有?

可能我理解的,或者说我书写的有误,欢迎大家指出。

继续阅读