Prototype 的秘密

1,565 阅读4分钟

写这篇文章的原因是,今天下班前在 Chrome Dev Tools 上瞎打了几行代码,意外发现自己并不了解 prototype 。庆幸不是在面试过程被问到,赶紧把漏洞补上。

先是打了下面两行代码,输出结果分别为 true, false。

Function.__proto__ == Function.prototype
// true
Object.__proto__ == Object.prototype
// false

实际上因为写代码的时候从没用过__proto__,(语言规范也建议不要去直接使用,尽管现代浏览器都实现了它。)所以,输出什么我都觉得挺正常的。

记得之前蔡阳问我的时候,我信誓旦旦地说 __proto__ 和 prototype 应该是一个东西吧,都是指向原型对象的引用。[\捂脸]

我们都知道,在 JavaScript 的世界里,万物皆为对象,这门 bug 一样的语言没有和其他妖艳贱货同流合污,搞什么面向对象!不,我们使用原型模式来进行扩充。复制是原型模式的基础,现代浏览器里面通过 __proto__ 属性把对象链接起来,当引擎去查找对象上的某个属性,先在该对象上查找,找不到的时候,顺着原型链往上找,找到了直接返回,找不到继续往上找,直到为 null 为止。仿佛链上的数据就是自己的属性一般。

那么,__proto__ 和我们写插件的时候用的 Plugin.prototype 是什么关系呢?事实上,__proto__是对构造器的原型对象的引用,大部分情况下,下面的代码返回 true。(Object.create 是个例外)

obj.__proto__ === obj.constructor.prototype
// true

所以关系是:
A.prototype 指向原型对象,这个原型对象是所有 A 创建出来的对象所共享的,所以我们常常把那些类共享的方法和数据追加到 prototype 对象上。而 __proto__ 是 A 构造出来的对象上,指向构造器的原型对象的引用。

function A() {
}
var a = new A();

A.prototype === Function.prototype 
// false

A.__proto__ === Function.prototype
// true

a.__proto__ === A.prototype
// true

迷糊吗? 其实你把代码捋一捋就好了。 A 是一个构造器,但实际上它也是个函数,只不过大写了。在 JavaScript 世界里面,我们有个不成文的规定,构造器首字母大写,你不大写也不会报错。所以你常见的,Object、Function、Array、String 都是构造器,也是函数对象。

事实上,JavaScript 里面的所有对象都拥有一个 __proto__ 的引用,这是原型链的基础,函数也是对象,所以 A 的 __proto__ 指向函数构造器原型对象,也就是 Function.prototype。 而 JavaScript 还为 class A 创建了一个匿名对象,这个对象是所有通过 new 出来的实例共享的,通过 new A 创建出来的实例都搭好了原型链,__proto__ 就指向那个匿名对象。叫什么名字好呢? 这不重要,引擎告诉你,可以通过 A.prototype 去引用到那块空间,那个匿名对象上还有一个 constructor 指针指回构造器 A。

迷迷糊糊,继续探索的脚步,我又输入下面的代码:

Object instanceof Function
// true
Function instanceof Object
// true

又一次懵逼了,仿佛是在说,A 是 B 的爸爸,同时 A 又是 B 的儿子。什么鬼嘛,确定不是 bug 吗?,怎么吹下去呢!我们通常使用 a instanceof b , 这样的表达式用来判断 a 是否为 b 的实例,引擎是怎么判断的呢?简单的说就是判断在 a 的原型链上是否存在 b.prototype。

那原型链又靠什么来链接? 毛错,__proto__。 这里有个很巧(shen)妙(jin)的设计,我想了很久。

Object.__proto__ === Function.prototype
// true

你注意看,跟一开始的例子不一样。上面的等式已成立,就好理解了。Object 的原型链上可以找到 Function.prototype,Object instanceof Function 成立,也合理,Object 就是一个 Funciton 嘛。而 Function 本来就是 Object ,没毛病(好心虚)。

看下图可能比较好理解,来自(Stack Overflow

prototype

JavaScript 的设计就是这样子的,非常完美(不然还能怎样呢?)
巩固一下吧。

typeof Object
// "function"
typeof Function.prototype
// "function"
Function.__proto__ == Function.prototype
// true
Object.__proto__ == Object.prototype
// false
Object.__proto__ == Function.prototype
// true
Function.prototype.__proto__ == Object.prototype
// true
Object.__proto__.__proto__ === Object.prototype.constructor.prototype
// true
Object.__proto__.prototype
// undefined
Function.prototype.__proto__ == Object.prototype
// true

总结规律

  1. 函数对象有 __proto__ 属性,又有 prototype 属性。普通对象只有 __proto__ 属性,没有 prototype 属性。

  2. Function.prototype 比较特殊,是个空函数,typeof Function.prototype 输出 “function” 但是它不能当成构造器,Function.prototype.prototype 为 undefined

  3. Object.prototype.__proto__ 为 null,原型链到这里结束。

推荐阅读

如需转载,请注明出处: w3ctrain.com/2017/01/24/… ,欢迎加入前端Q群(467969149)