22

上帝视角一文理解JavaScript原型和原型链

 3 years ago
source link: https://segmentfault.com/a/1190000037427760
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文呆鹅原创,原文地址: https://juejin.im/user/307518987058686/posts

前言

本文将从 上帝角度 讲解JS的世界,在这个过程中,大家就能完全理解JS的原型和原型链是什么,之后还会基于原型和原型链知识拓展一些相关知识。

阅读本文前可以思考下面三个问题:

  • 你理解中的原型和原型链是什么?
  • 你能完全理解并画出原型和原型链的关系图吗?
  • 基于原型和原型链拓展的相关知识你了解多少?

经典图

大家接触原型和原型链时应该都看到过下面这张图。刚开始了解的时候,看到这个图大都不太明白,甚至一脸懵,这里先留个坑。

mAFJbuq.jpg!mobile

先祭图,让暴风雨来得更猛烈些!

下面开始讲解JS世界是如何一步步诞生的,看完你也就完全明白这张神图啦。

无中生有

起初,上帝JS掌控的世界什么都没有。

上帝JS说:没有东西本身也是一种东西啊,于是就有了 null

yqqmiaQ.jpg!mobile

现在我们要造点儿东西出来。但是没有原料怎么办?

有一个声音说:现在不是有 null 了嘛?

上帝说:那就无中生有吧!

U7jqY3y.png!mobile

JavaScript中的1号对象产生了,不妨把它叫做机器1号。

这个机器1号可不得了,它是JS世界的第一个对象,它是真正的万物始祖。它拥有的性质,是所有的对象都有的。

__proto__ 是什么呢?是“生”的意思,或者换成专业点的叫法“继承”。

有中生有

刚开始造物,上帝当然想继续下去啦,既然已经有了一个始祖级的机器,剩下就好办了,因为一生二,二生三,三生万物嘛。

不过上帝很懒,他不想一个一个地亲手制造对象。于是他做了一台能够制造新对象的东西:

QjuuEn.png!mobile

他给这个东西起了一个名字:Object。

但是这个Object制造对象时候,需要有一个模版,现在只有机器1号,它就取了机器1号当模版。图中的 prototype 就代表模板对象。

如何启动制造呢?通过 new 命令。你按下“new”按钮,新的对象就造出来了。

63IbqaY.png!mobile

把这个过程写成代码就是:

var obj = new Object();

轰轰烈烈的造物运动开始了……

有生万物

有一天,上帝JS去看了上帝Java造的世界,发现上帝Java的世界好精彩,可不仅仅有Object对象,还有String对象、Number对象、Boolean对象等等。

于是上帝就思考了:那我可以多让机器造一些对象啊。

但是上帝觉得把这些工作都交给机器1号的话,机器1号太累了,不如让机器1号造一个机器2号来做这些工作。

重点说明下“这些工作”指的是:总体负责制造所有的对象,包含Object、String、Number、Boolean、Array,甚至还有之后的Function。当然它只是负责制造,并不一定会亲手去制造对象,可以通过制造对应的机器来帮助它制造对象

于是就有了机器2号:

RBjeY3N.png!mobile

(注: __proto__ 写起来麻烦,我们之后用 [p] 来代替)

可能有的小伙伴注意到啦, Object 也指向了机器2号,这是因为机器2号是负责造对象的,当然也负责造 Object 对象啦。

接下来,既然机器2号是由机器1号造出来的,而 String、Number、Boolean、Array 这些对象是由机器2号造出来的,所以它们其实和 Object 一样,也自带了 new 命令:你按下“new”按钮,新的对象就造出来了。

但是Object有自己的模板:机器1号。而 String、Number、Boolean、Array 它们有模板吗?

其实机器2号在创建它们的时候并不是直接创建它们的,而是先创建了对应对象的机器作为模板,然后再由各自的机器来创建它们。

具体我画了String相关的图(其他Number、Boolean、Array等都是同一个道理的):

VrQfiie.png!mobile

这样,这张图显示了JS世界中那些最基本的机器本身的原型链,以及它们的模板对象的原型链。

  • 机器1号制造了机器2号,机器2号总体负责各种对象的制造
  • 但是机器2号并不是直接造各种对象,而是通过先建造对应的机器,再由对应的机器来制造对象。如:对于String, 机器2号 先制造了 机器String号 ,然后由 机器String号 来制造String, 机器2号 只负责总体控制
  • 虽然机器2号制造了各种各样的机器,但是因为机器2号是由机器1号制造的,所以这些被制造的机器所属权还是归于机器1号的,毕竟机器1号是始祖级的。
  • 对象和对应的机器,通过 prototype 来连接。如:对于String, 机器2号String 通过 prototype 连接
  • 每个机器都有且只有一个的对象,每个对象也都有且只有一个机器作为模板。

万物缺活力

上帝看着越来越丰富的世界非常高兴,但是总感觉缺点什么?

一个声音说:世界缺少活力呀

上帝说:那就造一个 能让世界动起来的对象

上帝给这个新对象的起了个名字叫:Funciton

但是这个制造Function的工作交给谁好呢,让世界动起来当然是非常重要的,那就交给机器2号吧, 由机器2号亲手负责Function的制造

于是,Function对象就出现了

让我们来观察一下Function对象:

prototype
__proto__

于是我们得到了Function的一个非常特别的性质:

Function.__proto__ === Function.prototype

于是JavaScript的世界的变成了下面的样子:

zIvquq3.png!mobile

到现在我们能明白啦:

  • 机器1号 = Object.prototype
  • 机器2号 = Function.prototype
  • 机器String号 = String.prototype

世界动起来

自从有了Function,世界就越来越有活力了,有什么事需要做,用new Function()造个新Function来做就行了。

但是刚造出来的Function机器 很难用 ,用法就像下面这个:

let Foo = new Function("name", "console.log(name)");

Foo('dellyoung'); // 控制台打印出:dellyoung

你想要造一个Function,无论是输入的内容(参数)还是要做的事情(函数体)都得弄成字符串,才能成功造出来。

上帝用起来难受啊,他就改装了一下这个Function,给他来了个语法糖

function Foo(name) {
    console.log(name);
}

Foo('dellyoung'); // 控制台打印出:dellyoung

(注:上面两段代码是完全等价的。)

现在造一个新的Function就舒服多啦!

以造 Foo() 为例,于是JavaScript的世界的变成了下面的样子:

VNn6FfF.png!mobile

Function这个对象比较特殊,它new出来后,就是一个全新的对象了, function Foo() (注意:它等价于 let Foo = new Function() )和 ObjectStringNumber 等这些对象一样,都是对象。

既然都是对象,当然 function Foo() 也是由机器2号来控制制造的,但是机器2号很忙,它没有精力直接制造 function Foo() ,机器2号是通过制造出一个 制造function Foo()的机器 来制造 function Foo()

咱们称 制造function Foo()的机器机器Foo()号

当然既然是机器,所以 机器Foo()号 也是 由机器1号 控制的,原因上文讲过:

虽然机器2号制造了各种各样的机器,但是因为机器2号是由机器1号制造的,所以这些被制造的机器所属权还是归于机器1号的,毕竟机器1号是始祖级的。

而且这个 function Foo() 对象制造出来后,它既然是对象,所以它和Object、String、Number等对象一样,可以通过 new Foo() 制造出新的对象,模板就是用的 机器Foo()号

听起来好像有点绕,咱们看看图就明白啦

r2UzMja.png!mobile

上图中:

Object.prototype
Function.prototype
String.prototype
Foo.prototype
__proto__

回到现实

现在我们就能完全理解并完整的画出原型和原型链的关系图啦:

n6BfymE.png!mobile

其实可以被用来 new 的对象或函数,我们都可以称之为构造函数,每个构造函数都和它的机器(也就是 XXX.prototype )通过 constructor 相连,我们来画出构造函数和它们的 constructor

riAbiyQ.png!mobile

为了清晰一些,上图用 [con] 表示 constructor

现在这张图就是完整的原型和原型链的关系图啦

用正式的语言总结一下就是:

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类。
  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

现在我们再看这张神图:

mAFJbuq.jpg!mobile

是不是很简单啦,不过就是把咱们画的图向右旋转了90°。

而且仔细看一遍,咱们的关系图包含的更加的全面。

填坑完毕。下篇文章我会以此为延伸,从底层讲解JavaScript的this,看完你会彻底理解this为何物,关注我

看完两件事

  • 欢迎加我微信(iamyyymmm),拉你进技术群,长期交流学习
  • 关注公众号「呆鹅实验室」,和呆鹅一起学前端,提高技术认知

mQBb6zU.jpg!mobile

:rainbow: 点个赞支持我吧 :rainbow:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK