苗疆三刀的随手记 2019-06-26
JavaScript里任何东西都是对象,任何一个对象内部都有另一个对象叫__proto__,即原型,它可以包含任何东西让对象继承。当然__proto__本身也是一个对象,它自己也有自己的__proto__,这样一级一级向上,就构成了一个__proto__链,即原型链。当然原型链不会无限向上,它有个终点,可以称为原型链的顶端,或者root,它是一个特殊的对象,它的__proto__为null。
obj.__proto__.__proto__......__proto__ === null;
但是对象并不是凭空产生的,它一般是某一个class,或者确切说是构造函数的实例。JavaScript和其它面向对象的语言不太一样,它没有所谓的class,而是用函数来模拟class。定义了一个函数,实际上就定义了一个class,函数本身就是class的constructor,例如:
function foo() {} var a = new foo();
这里创建了一个对象a,是foo的一个实例。既然a是一个对象,它就有原型__proto__,那a的__proto__是怎么设定的?这里就引出了prototype,它是函数才有的一个属性,也是一个对象,一个函数创建的实例的__proto__就指向这个函数的prototype。所以a的__proto__被设定为foo.prototype,即:
a.__proto__ === foo.prototype;
当然foo.prototype也是个对象,它自然也有__proto__,默认指向Object.prototype,即:
foo.prototype.__proto__ === Object.prototype;
而Object.prototype已经没有原型了,它的__proto__是null。这是JavaScript里一个很特殊的对象,它就是原型链的顶端。
以上就构成了由对象a开始的原型链:
a.__proto__ === foo.prototype; a.__proto__.__proto__ === Object.prototype;
JavaScript因此而推断出:
a instanceof foo === true; a instanceof Object === true;
注意,这就是JavaScript判断一个对象是否instanceof某个函数的依据,即对象a的原型链上有没有一个__proto__是这个函数的prototype,如果有,那么a就是这个函数的instance。由于一般所有的原型链最终都会指向顶端的Object.prototype,所以它们都是Object的instance。
我们可以设定
foo.prototype = { x : 2, y : 3, }
这里用字面量创建一个对象赋给了foo.prototype,这样foo所创建出来的任何对象的__proto__就都指向了它,因此它们就可以访问里面的field,或者说可以认为它们继承了这些field,例如:
var a = new foo(); a.x === 2; a.y === 3;
不只是foo.prototype,继续往上foo.prototype的__proto__,以及原型链上所有的__proto__都会被这个对象继承。一般的对象的很多常用方法如toString,都是从原型链顶端的Object.prototype里继承来的。
上面说了,JavaScript里任何东西都是对象,包括函数,可以称为函数对象。所以foo也是对象,那foo的原型__proto__是什么?它是谁的instance?
JavaScript里定义了一个特殊的函数叫Function,可以称作是所有函数的爸爸,所有的函数都是它的实例,因此你可以认为,定义foo的时候发生了这样的事情:
var foo = new Function(args, function_body);
于是我们有:
foo.__proto__ === Function.prototype; foo instanceof Function === true;
注意这里的Function.prototype,这也是JavaScript里一个特殊的对象,Chrome的console里要是输入Function.prototype,根本什么也打印不出来,什么native code,就是说它是内部实现的。
这个原型链还没到顶,Function.prototype仍然有原型__proto__,指向Object.prototype,所以我们最终有:
foo.__proto__.__proto__ === Object.prototype; foo instanceof Object === true;
现在有个问题来了,那Function自己呢?它其实也是个函数,也是个对象,它的__proto__指向谁?答案是它自己的prototype,即:
Function.__proto__ === Function.prototype; Function instanceof Function === true; Function.__proto__.__proto__ === Object.prototype; Function instanceof Object === true;
总结一下:所有的函数都是Function的instance,Function自己也是它自己的instance,不过后者严格来说并不准确,Function并不是它自己创造自己的,而应该看作JavaScript里原生的一个函数对象,只不过它的__proto__指向了它自己的prototype而已。
这是JavaScript比较奇葩的一个地方,也是不太让人容易接受的一点。
我们知道一般任何对象都是Object的instance,因为原型链的顶端都指向Object.prototype。那么Object本身是什么?Object也是个函数,而任何函数都是Function的实例对象,比如Array,String,当然Object也包括在内,它也是Function的实例,即:
Object.__proto__ === Function.prototype; Object instanceof Function === true
同时,Function是个对象,它的原型是Function.__proto__,指向Function.prototype,并且这个原型链向上继续指向Object.prototype,即:
Function.__proto__.__proto__ === Object.prototype; Function instanceof Object === true
这样就有了一个JavaScript里经常说到的蛋鸡问题:
Object instanceof Function === true Function instanceof Object === true
到底谁先谁后,谁主谁次?关于这一点网上已经有很多解释,这里首先陈述我的观点,是先有Function,它是主;后有Object,是次。以下是我个人的理解,可能并不准确。要看待这个问题,我们可以从JavaScript创造世界开始想象:
- 首先没鸡没蛋,先有一个特殊对象root_prototype,它是上帝。 - 接下来应该是先有Function,并且定义它的prototype和__proto__,都连上了root_prototype。 - 然后才有了Object,它是Function的instance,继承了Function。这时候Object仍然只是个普通的函数。 - 然后规定Object.prototype = root_prototype,这时候Object才开始显得特殊,成为了原型链的顶端,否则它和其它函数根本没什么区别。 - 于是所有的东西,包括Function,都成了Object的instance了。
这里要强调Object和其它函数的不同之处。Object之所以特殊,就是因为Object的prototype被设定为了root_prototype,仅此而已;而其它函数例如foo,它的prototype只是一个普通的对象,这个对象的__proto__默认情况下指向root_prototype。至于为什么这样设定,为什么Object会特殊化,大概只是因为Object这个名字起得好,而foo,bar没那么特殊。所以说白了Object函数只是一个盛放root_prototype的容器而已,从而使它晋升为一个特殊的函数。
另外值得注意的是,obj instanceof function 并不意味着obj就是这个function创建出来的,只不过是obj的原型链上有function.prototype而已。
所以所谓的 Object instanceof Function 和 Function instanceof Object 的蛋鸡问题,前者应该来说是自然而然、不容置疑的,可以认为Object函数是Function创造出来的;而后者说白了只是因为强行规定了Object函数的特殊性,而导致的一个推论,而Function并非是Object创建的。
当然这些概念绕来绕去讨论其实我感觉没什么很大意义,无非只是自圆其说而已。大家写代码时也不会太纠结这些问题,重点还是要把原型链搞清楚。