JavaScript 原型中的哲学思想
作者按:
学原型链的时候感觉颇有些哲学思想在里面,于是搜到这样一篇文章,觉得很有意思,记录下来。
文章说 Object 和 Function 犹如 JavaScript 世界中的亚当与夏娃,我倒觉得原型家族也是个母系氏族社会呢,Object 为母,Function 为父,比较像我国古代的女娲造人,因为 Function.prototype 也指向 Object.prototype
以下是正文:
记得当年初试前端的时候,学习 JavaScript 过程中,原型问题一直让我疑惑许久,那时候捧着那本著名的红皮书,看到有关原型的讲解时,总是心存疑虑。
当在 JavaScript 世界中走过不少旅程之后,再次萌发起研究这部分知识的欲望,翻阅了不少书籍和资料,才搞懂__proto__ 和 prototype 的概念。
故以作此笔记,日后忘了可以回来看看。
如果你看的过程中觉得理解有些困难,把例子在代码中跑一跑,亲手试一试也许能解决不少疑惑。
一切皆为对象
殊不知,JavaScript的世界中的对象,追根溯源来自于一个 null
「一切皆为对象」,这句着实是一手好营销,易记,易上口,印象深刻。
万物初生时,一个 null 对象,凭空而生,接着 Object、Function 学着 null 的模样塑造了自己,并且它们彼此之间喜结连理,提供了 prototype 和 constructor,一个给子孙提供了基因,一个则制造万千子子孙孙。
在 JavaScript 中,null 也是作为一个对象存在,基于它继承的子子孙孙,当属对象。
乍一看,null 像是上帝,而 Object 和 Function 犹如 JavaScript 世界中的亚当与夏娃。
原型指针 proto
在 JavaScript 中,每个对象都拥有一个原型对象,而指向该原型对象的内部指针则是__proto__,通过它可以从中继承原型对象的属性,原型是 JavaScript 中的基因链接,有了这个,才能知道这个对象的祖祖辈辈。从对象中的__proto__可以访问到他所继承的原型对象。
1 |
|
上面代码中,创建了一个 Array 的实例 a,该实例的原型指向了 Array.prototype。
Array.prototype 本身也是一个对象,也有继承的原型:
1 |
|
// 等同于 Array.prototype.proto === Object.prototype
这就说了明了,Array本身也是继承自Object的,那么Object的原型指向的是谁呢?
1 |
|
所以说,JavaScript 中的对象,追根溯源都是来自一个null对象。佛曰:万物皆空,善哉善哉。
除了使用.__proto__方式访问对象的原型,还可以通过 Object.getPrototypeOf 方法来获取对象的原型,以及通过 Object.setPrototypeOf 方法来重写对象的原型
。
值得注意的是,按照语言标准,__proto__属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用 Object.getPrototypeof 和 Object.setPrototypeOf ,进行原型对象的读写操作。
这里用__proto__属性来描述对象中的原型,是因为这样来得更加形象,且容易理解。
原型对象 prototype
函数作为 JavaScript 中的一等公民,它既是函数又是对象,函数的原型指向的是Function.prototype
1 |
|
函数实例除了拥有__proto__属性之外,还拥有 prototype 属性。
通过该函数构造的新的实例对象,其原型指针 proto 会指向该函数的 prototype 属性。
1 |
|
而函数的 prototype 属性,本身是一个由 Object 构造的实例对象。
1 |
|
prototype属性很特殊,它还有一个隐式的 constructor ,指向了构造函数本身。
1 |
|
原型链
概念
原型链作为实现继承的主要方法,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(proto)。
那么,假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。
如此层层递进,就构造了实例与原型的链条,这就是原型链的基本概念。
意义
“原型链”的作用在于,当读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。以此类推,如果直到最顶层的 Object.prototype 还是找不到,则返回 underfine
亲子鉴定
在 JavaScript 中,也存在鉴定亲子之间DNA关系的方法:
instanceof
运算符返回一个布尔值,表示一个对象是否由某个构造函数创建。
Object.isPrototypeOf()
只要某个对象处在原型链上,isProtypeOf 都返回true
1 |
|
要注意,实例b的原型是 Bar.prototype 而不是 Bar
一张历史悠久的图
这是一张描述了 Object、Function 以及一个函数实例 Foo 他们之间原型之间联系。如果理解了上面的概念,这张图是不难读懂。
从上图中,能看到一个有趣的地方。
Function.prototype.proto 指向了 Object.prototype,这说明 Function.prototype 是一个 Object实例,那么应当是先有的Object再有Function。
但是Object.prototype.constructor.proto 又指向了 Function.prototype。这样看来,没有Function,Object也不能创建实例。
这就产生了一种类「先有鸡还是先有蛋」的经典问题,到底是先有的Object还是先有的Function呢?
这么哲学向的问题,留给你思考了。
我只是感慨:
越往JavaScript的深处探索,越发觉得这一门语言很哲学。
先有鸡还是先有蛋?
时隔半年,偶尔翻开这篇文章。
对于这个问题,又有了新的思考。
愿意跟能看到这里的你来分享一下。
我们可以先把 Object.prototype 和 Function.prototype 这两个拎出来看,因为他们本身就是一个实例对象。
为方便理解,我们改一下名字,避免和 Object 和 Function 的强关联,分别叫:Op 和 Fp
那么就有这样的原型链存在了
我再描述一下上面的原型链,先有 null , 再有了 Op , 然后再有了 Fp ,然后以 Fp 为原型的两个构造函数 (Object, Function) 出现了。
而作为构造函数,需要有个 prototype 属性用来作为以该构造函数创造的实例的继承。
所以Object.prototype = Op, Function.prototype = Fp。
原文链接:JavaScript 原型中的哲学思想
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!