javascript中的 prototype, __proto__
, constructor 与 原型继承链
综述
三个值的含义
- 每个对象都有自己的的
__proto__
,这个属性指向自己的构造函数(constructor
)的prototype
,即
obj.__proto__ === obj.constructor.prototype
- 在javascript中只有函数有自己的
prototype
大部分对象并没有自己的prototype
,因为prototype
表示实例共享的属性,而只有函数可以生成实例,所以…
这里要格外强调一下constructor
- constructor并不是实例自己的属性,每个实例的constructor都是一样的,指向构造函数
- constructor是原型的属性,因此如果改动原型(重新给原型赋值),会导致constructor不正确
例如:
function a() {}
function b() {}
b.prototype = new a();
console.log((new a()).constructor); //a()
console.log((new b()).constructor); //a()
那么,这个东西有什么用呢?
答:大部分情况是无用的,具体可参考知乎的讨论JavaScript 中对象的 constructor 属性的作用是什么?
javascript中什么是对象
- javascript中所有非字面值都是对象
- 字面值中函数和对象仍然是对象,可以使用
{} instanceof Object
来验证
注意这里我强调了字面值这个概念,如下
// 文中所有测试环境均为 firefox 48
console.log("" instanceof Object); //false
console.log(null instanceof Object); //false
console.log(undefined instanceof Object); //false
console.log(1 instanceof Object); //false
console.log(true instanceof Object); //false
function foo() {}
console.log(foo instanceof Object); //true
console.log({} instanceof Object); //true
- 字面值应使用
typeof
去判断类型 instanceof
测试一个对象在其原型链构造函数上是否具有prototype属性。- 这与python是不同的,python中一切都是对象,包括字面值
具体可查阅相应的MDN文档
深入探讨
从上述中其实就可以对javascript的原型链略知一二,为了更好的看清各个属性的关系,我们来看看一些数据// print作用是将每个对象的属性直接用console.log输出
function foo() {}
var ff = new foo();
var o = {};
print([ff, foo, Function, o, Object]);
print([foo.prototype, Function.prototype, Object.prototype]);
整理得出的表格如下:
self | prototype | constructor | __proto__ |
---|---|---|---|
foo {} | undefined | foo() | foo {} |
foo() | foo {} | Function() | function() |
Function() | function() | Function() | function() |
Object {} | undefined | Object() | Object {} |
Object() | Object {} | Function() | function() |
上述表格中,可以一行行分析,可以看出
- ff是实例对象,所以无原型
- 函数的构造器是Function()
- Function()也是自己的构造器,其
prototype
等于__proto__
(代码并没有验证这一点,但是可以测试得出此结论) - 普通对象由Object()函数生成,而Object()的构造器是Function()
可能会有几个问题
ff.__proto__
是foo {}
,也就是foo.prototype
的原型,这是什么?- Function()的原型function()究竟是个什么东西?
解答第一个问题:
当然,原型是个普通对象,所以第二行的foo {}
和第五行的Object {}
都是同一种对象,那为什么有一个是foo而另一个是Object呢?
- 可以做个小实践,用函数表达式赋值给一个变量,会生成一个匿名函数,检查这个变量的
__proto__
发现也是Object{} - 所以,其实foo就是这个对象的名字,用console.log输出出来就把名字附加上了
我们来直接来看看上述所有原型的这三个属性
self | prototype | constructor | __proto__ |
---|---|---|---|
foo {} | undefined | foo() | Object {} |
function() | undefined | Function() | Object {} |
Object {} | undefined | Object() | null |
可以看出:
- 函数原型是对象,函数原型的构造器就是函数本身
- 从这个表格,也看出了原型链,即:
ff.__proto__(foo {}) --> foo.prototype.__proto__(Object {}) --> foo.prototype.__proto__.__proto__(null)
foo.__proto__(function()) --> foo.__proto__.__proto__(Object {}) --> foo.__proto__.__proto__.__proto__(null)
o.__proto__(Object{}) --> o.__proto__.__proto__(null)
- 原型链的尽头(root)是Object.prototype。所有对象均从Object.prototype继承属性。
细心的人可能发现,上面出现了一个关于鸡和蛋的问题:
- 函数是对象:函数Function的原型链(与foo的原型链一致)上出现了Object{}(Object的原型)
- 对象由函数生成:Object本身就是一个函数(这从第一个表中Object的constructor就是Function可以看出)
你也可以用代码验证:
console.log(Function instanceof Object); //ture console.log(Object instanceof Function); //true
那么问题来了,究竟是先有Object还是Function呢?
- 很多语言里都存在一个鸡和蛋的问题
如何实现可以看知乎的讨论先有Class还是先有Object?,摘文中的前几段
简短答案:“鸡・蛋”问题通常都是通过一种叫“自举”(bootstrap)的过程来解决的。 其实“鸡蛋问题”的根本矛盾就在于假定了“鸡”或“蛋”的其中一个要先进入“完全可用”的状态。而许多现实中被简化为“鸡蛋问题”的情况实际可以在“混沌”中把“鸡”和“蛋”都初始化好,而不存在先后问题;在它们初始化的过程中,两者都不处于“完全可用”状态,而完成初始化后它们就同时都进入了可用状态。 打个比方,番茄炒蛋。并不是要先把番茄完全炒好,然后把鸡蛋完全炒好,然后把它们混起来;而是先炒番茄炒到半熟,再炒鸡蛋炒到半熟,然后把两个半熟的部分混在一起同时炒熟。
什么情况下会出现鸡和蛋的问题呢?
就是声明一个包含所有集合的集合啊!好了,你们知道这是罗素悖论,但并不妨碍PL中这样设计。
Function.prototype
Function.prototype是个不同于一般函数(对象)的函数(对象)(作为一个函数,并没有原型)
从ECMAScript Specification的Function.prototype摘录几点:
The initial value of Function.prototype is the standard built-in Function prototype object (15.3.4).
The Function prototype object is itself a Function object (its [[Class]] is "Function") that, when invoked, accepts any arguments and returns undefined.
The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object (15.2.4). The initial value of the [[Extensible]] internal property of the Function prototype object is true.
The Function prototype object does not have a valueOf property of its own; however, it inherits the valueOf property from the Object prototype Object.
The length property of the Function prototype object is 0.
图
一张比较经典的图是:
近期,在知乎发现了画得非常好而且全面的图,可以参考此篇文章Javascript的原型链图(原创 知乎首发)