Open creeperyang opened 9 years ago
Object
和Function
的鸡和蛋的问题ES5关于Object
和Function
的规定:
从上面的规定再结合其它,理出以下几点:
原型链的尽头(root)是Object.prototype
。所有对象均从Object.prototype
继承属性。
Function.prototype
和Function.__proto__
为同一对象。
这意味着: Object
/Array
/String
等等构造函数本质上和Function
一样,均继承于Function.prototype
。
Function.prototype
直接继承root(Object.prototype
)。
通过这点我们可以弄清 继承的原型链:Object.prototype(root)
<---Function.prototype
<---Function|Object|Array...
。 如下图所示:
以上3点比较容易理解,或者说规范里就这样定义的。由以上3点导出我们最后的问题:Object
和Function
的鸡和蛋的问题。
回答这个问题,必须首先更深入一层去理解Function.prototype
这个对象,因为它是导致Function instanceof Object
和Object instanceof Function
都为true
的原因。
回归规范,摘录2点:
Function.prototype
是个不同于一般函数(对象)的函数(对象)。
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.
Function.prototype
像普通函数一样可以调用,但总是返回undefined
。- 普通函数实际上是
Function
的实例,即普通函数继承于Function.prototype
。func.__proto__ === Function.prototype
。Function.prototype
继承于Object.prototype
,并且没有prototype
这个属性。func.prototype
是普通对象,Function.prototype.prototype
是null
。- 所以,
Function.prototype
其实是个另类的函数,可以独立于/先于Function
产生。
Object
本身是个(构造)函数,是Function
的实例,即Object.__proto__
就是Function.prototype
。
The value of the [[Prototype]] internal property of the Object constructor is the standard built-in Function prototype object.
The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is "Object", and the initial value of the [[Extensible]] internal property is true.
最后总结:先有Object.prototype
(原型链顶端),Function.prototype
继承Object.prototype
而产生,最后,Function
和Object
和其它构造函数继承Function.prototype
而产生。
十分感谢
会玩!
@Kelichao 哈哈,欢迎提出见解。
相当绕啊,不过终于让我明白透了这关系。
再次观摩男神。。再学习一遍
这东西感觉要隔一段时间就看,不然很容易被绕迷糊。 感觉把Object.prototype和Function.prototype这两看成浏览器的私货,比如下面这操蛋的一对:
typeof Object.prototype === 'object' && (Object.prototype instanceof Object === false)
typeof Function.prototype === 'function' && (Function.prototype instanceof Function === false)
@simongfxu typeof 这个操作符跟原型链没什么必然的关系. typeof null 还等于 'object'呢.
get
为什么我试了一下第一个例子都是返回false,用的是chrome。
@igblee 截图看看
学习一下
我喜欢你画的图,好有感觉。么么哒
又被骗进来了吧😄 @Evllis
@Evllis 忘记从哪偷的图了... 很久很久以前就有这张图了。
Update: 找不到图片具体哪来的,但目前看起来,一个可信的来源是 mollypages.org
图是从王福朋那里来的 他写的一系列关于原型链的文章,大家伙可以去看看
http://www.cnblogs.com/wangfupeng1988/p/4001284.html
@bonzstars 是这个吗?前不久刚看完,还得多看几次。
顺便请教博主一个问题:@creeperyang
在JavaScript中,Function构造函数本身也算是Function类型的实例吗? Function构造函数的prototype属性和proto属性都指向同一个原型,是否可以说Function对象是由Function构造函数创建的一个实例?
还有就是JavaScript 里 Function 也算一种基本类型吗?Function继承了Object的所有属性,那为什么还要单独搞个Function,直接在Object延伸,然后其他类型继承Object,非要转个弯。
感觉看了这篇文章,以前的疑问又出来了,就是这个Function.proto === Function.prototype的问题。
其实我们可以先看这样一个问题:
Object.prototype
是对象吗?
An object is a collection of properties and has a single prototype object. The prototype may be the null value.
这是object的定义,Object.prototype
显然是符合这个定义的。Object.prototype
并不是Object
的实例。 这也很好理解Object.prototype.__proto__
是null
。这已经某种程度上解开了鸡和蛋的问题:Object.prototype
是对象,但它不是通过Object
函数创建的。
下面我们来看Function/Function.prototype
:
似乎可以看出一点东西:
在chrome的console中,Array.prototype
以数组的形式输出,Map.prototype
以Map的形式输出,Function.prototype
输出function () { [native code] }
... 可以反过来讲继承了某个prototype
,console就认为是对应的类型(以prototype
来判断)。
就上图而言,我们有疑惑的其实就是为什么 Function.prototype
和 Function.__proto__
是同一个对象?
Function
本身也是function。Function.prototype
是所有function的原型(包括Function
自己)。Function.prototype
和Function
并没有反向的什么关系(除了正向的Function
继承了Function.prototype
)。所以疑惑就可以解除了:Function.prototype
和 Function.__proto__
相同不代表Function
这个函数是由自身创建的。先有了Function.prototype
这个对象(其实也是函数,下面说明),然后才有了其它函数而已。
那么问题来了,Function.prototype/Function.__proto__
是 function 吗(对比开头的问题)?
当然是。比如我们可以正常执行Function.prototype()
。当然,还是看定义更好:
member of the Object type that may be invoked as a subroutine. In addition to its properties, a function contains executable code and state that determine how it behaves when invoked. A function’s code may or may not be written in ECMAScript.
ECMAScript function objects encapsulate parameterized ECMAScript code closed over a lexical environment and support the dynamic evaluation of that code. An ECMAScript function object is an ordinary object and has the same internal slots and the same internal methods as other ordinary objects.
然而 Function.prototype
不是 Function
的实例。
下面附加一幅图帮助理解:
用issue写博客。。。。。这奇葩的方式也是没谁了,用github pages发布个静态博客有那么难?
@eddiebai 这不是难不难的问题吧,issue 相比 github pages 更加方便吧...
你们怎么都来得这么快?都睡在GayHub里面吗?@rccoder
@jawil 邮件会 push 啊...
po主开放issue也不过是更方便讨论,尤其是这种技术性的东西,难免会有疏漏和误差,开issue没什么不妥,别那种质疑的口气,我不觉得po主连github pages都不知道。
github pages 是来供开发者展示自己项目的,用pages搭blog其实不妥,po主用issue没毛病
po主上面说 我们的操作符instanceof正是通过探测obj.proto.proto... === Constructor.prototype来验证obj是否是Constructor的实例。 如下
function a(){}
function b(){}
function c(){}
b.prototype = a;
c.prototype = a;
var cInstance = new c();
cInstance instanceof b/// true????
可是 cInstance 并不是 b的实例啊
@wangcansunking
假设你的 c.prototype = a; 写错了 应该是c.prototype = b;
cInstance instanceof b
是拿 cInstance.proto.proto...(无限递归proto属性) 跟 b.prototype比较
你这里
cInstance.__proto__ == c.prototype == b
cInstance.__proto__.__proto__ == b.__proto__ == Object.prototype
中间没有 b.prototype
应该这么写 才能得到你要的结果
c.prototype = new b()
有点奇怪Array.prototype
的类型是数组,但是又跟object
一样的写法和属性访问,而且Array.prototype.length
为0,不知道这是怎么实现的
@yangblink 我想要的是实现b继承a,c也继承a。 和同事讨论这个问题,他给我的答案是
function a(){}
function b(){}
function c(){}
b.prototype = new a();
c.prototype = new a();
var cInstance = new c();
cInstance instanceof b/// false
然后还有的推荐
function a(){}
function b(){}
function c(){}
b.prototype = new a();
b.prototype.constructor = b;
c.prototype = new a();
c.prototype.constructor = c;
var cInstance = new c();
cInstance instanceof b/// false
b.prototype.constructor = b;
这句话的作用是什么呢?
@wangcansunking
你试一下不重设构造函数c的原型的构造方法就知道,就是去掉c.prototype.constructor = c;
,然后看看cInstance instanceof a
构造函数声明的时候就会有prototype
属性,然后prototype
上会有constructor
方法,这个方法就是构造函数本身,但是你再b.prototype = new a()
的时候修改了它,使得它变成了构造函数a,如果不重设,那么这时候通过构造函数b创建出来的实例就会出现继承紊乱,你会发现这个实例它同时是b和a的实例
@bingchenqin
因为这个实现的是继承的机制,我在本地进行测试无论是否加
c.prototype.constructor = c
cInstance instanceof a
和cInstance instanceof c
都是 true
而本质上如果实现继承的思想的话,我想要的也就是这两个都是 true
如果是为了保持prototype
的一致性我可以理解,毕竟一个构造函数的prototype.constructor
要指向本身。
当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象吗?
@yuantingjun 是的,规范可以看 https://www.ecma-international.org/ecma-262/6.0/#sec-functionallocate
精华贴
学习了
学习了学习了,大神总结到位,一目了然.
首先来说说prototype属性,不像每个对象都有proto属性来标识自己所继承的原型,只有函数才有prototype属性。
这里的__proto__
是不是应该是prototype
?
这个真的是厉害了!! 不过我是来提问的,不知道大神会不会看到。。
function Parent(){}
Parent.prototype.hello = function(){
console.log("hello");
};
function Child(){
Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.haluo = function(){
console.log("haluo");
};
var c = new Child;
上面的代码仅仅是一个简单的继承,这没有问题。但是我在 chrome devtools 里面发现问题了(可能要劳烦大神写个html测试一下了)。
简单的说明:在 devtools DEBUG 模式看 Scope 变量,这个变量后面会显示它的来源信息,也正是这个来源信息让我产生了疑惑。
用 devtools 观察实例对象 c
,c.__proto__
属性没有问题,c.__proto__.__proto__
属性也没有问题,有问题的是 c.__proto__
后面显示的是 Parent
, c.__proto__.__proto__
后面显示的是 Object。这我就无法理解了。c 作为一个实例对象,c.__proto__
后面的数据不应当是 Child 吗?(我用ES6重写了这段代码,也是这个现象)
作为对比,我查看了 Array , WebSocket 等对象的实例,它们的就没有这个问题,比如:
var x = [];
x.__proto__
的后面显示的是 Array,而且 x.__proto__
下面显示了 Array.prototype
的各种方法。
大神帮忙看看是不是我哪里理解的还不到位?O(∩_∩)O谢谢
@mystorp
c.__proto__
为什么显示 Parent
?
因为 c = new Child
,所以,c.__proto__
就是 Child.prototype
(即 Object.create(Parent.prototype)
)。
问题即可以转化为 var x = Object.create(Parent.prototype)
, 为什么 x
显示为 Parent
。
很简单啊,Object.create
就是指定 x.__proto__
为 Parent
,x
就是 Parent
的实例,x instanceof Parent // true
。
c.__proto__.__proto__
为什么显示成 Object
?
同上,问题即 Parent.prototype.__proto__
显示为 Object
。因为默认情况下 function.prototype
就是普通对象,继承 Object.prototype
。
@jawil http://louiszhai.github.io/2015/12/17/prototype/ 在JS里,对象构造器Object既是对象,又是构造器,也是函数。一图流:http://louiszhai.github.io/docImages/prototype.png
当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象
我试了一下:
> function bar() {}
> bar.prototype
{constructor: ƒ}
@deqing 你是对的,感谢指正。可能15年写的时候规范和浏览器中的行为还不够明确,但现在ES2015规范已经明确:
prototype 是一个有 constructor 属性的对象,不是空对象。
“Object和Function的鸡和蛋的问题”中的第2点: Function.prototype和Function.proto为同一对象。出现以下情况: console.log(Function.prototype === Function.__proto___); // false
《JavaScript权威指南》其实是一本很烂的书,不知怎么入了楼主的法眼。这个终极问题被你描述后,变得更加难懂了
技术博客搬到GitHub是一种很low的做法,直白点,就是吸星的
《JavaScript权威指南》是我初学JS的入门书籍,对我帮助很大,我本人不觉得它烂。另外《你不知道的JavaScript》非常好,推荐现在学JS的人看。
博客搬到 issue 的出发点是更利于相互讨论,共同进步。我写的东西不一定对,大家讨论指正是非常好的;同时也可以利用github的生态。至于 吸星,能吸星自然是再好不过了,有人表扬是件愉快的事。
@yunfeiyang27 大家更宽容一点,专注于技术是最好的。另外你有问题/建议/指责可以邮件我,不要过多干扰其他人的时间线。issue里面最好都是技术相关的。thx。
今天被问到proto和prototype在什么情况下相等,想半天没想出来,看了博主的解释,醍醐灌顶,感觉之前对原型链的理解还是浅了一些,学习了
谢谢大神的分享,另外我觉得用issue写博客,朴实,方便直接,写博客目的不外乎就是总结知识的同时分享,能讲清楚就行了,也不需要太华丽。
写的很精彩,获益匪浅,感谢。
另外关于 instanceof 的解释:
对象的proto指向自己构造函数的prototype。obj.proto.proto...的原型链由此产生,包括我们的操作符instanceof正是通过探测obj.proto.proto... === Constructor.prototype来验证obj是否是Constructor的实例。
有一个例外情况:
var num1 = 100;
var num2 = Number(100);
var num3 = new Number(100);
num1 === num2; // true
num1 === num3; // false
num2 === num3; // false
num1.__proto__ === Number.prototype; // true
num2.__proto__ === Number.prototype; // true
num3.__proto__ === Number.prototype; // true
num1 instanceof Number; // false
num2 instanceof Number; // false
num3 instanceof Number; // true
@whosesmile 此处修改答案, 你说的对, 是我考虑不周, 等我想到再来回复
@zxc5800 基本类型当然要遵循原型链规则,不然基本类型上就无法做原型链上的方法调用了啊:
var num = 100.001;
var str = 'hello';
console.log(num.toFixed(2)); // ok
console.log(str.substring(3)); // ok
就标题而言,这是七八篇里起得最满意的,高大上,即使外行人也会不明觉厉! :joy:
不过不是开玩笑,本文的确打算从
__proto__
和prototype
这两个容易混淆来理解JS的终极命题之一:对象与原型链。__proto__
和prototype
__proto__
引用《JavaScript权威指南》的一段描述:
翻译出来就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。好啦,既然有这么一个原型对象,那么对象怎么和它对应的?
对象
__proto__
属性的值就是它所对应的原型对象:上面的代码应该已经足够解释清楚
__proto__
了:grin:。好吧,显然还不够,或者说带来了新的问题:Object.prototype
是什么?凭什么说one
和two
的原型就是Object.prototype
?prototype
首先来说说
prototype
属性,不像每个对象都有__proto__
属性来标识自己所继承的原型,只有函数才有prototype
属性。为什么只有函数才有
prototype
属性?ES规范就这么定的。开玩笑了,其实函数在JS中真的很特殊,是所谓的一等公民。JS不像其它面向对象的语言,它没有类(
class
,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。当你创建函数时,JS会为这个函数自动添加
prototype
属性,值是空对象值是一个有 constructor 属性的对象,不是空对象。而一旦你把这个函数当作构造函数(constructor
)调用(即通过new
关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype
的所有属性和方法(实例通过设置自己的__proto__
指向承构造函数的prototype
来实现这种继承)。小结
虽然对不熟悉的人来说还有点绕,但JS正是通过
__proto__
和prototype
的合作实现了原型链,以及对象的继承。构造函数,通过
prototype
来存储要共享的属性和方法,也可以设置prototype
指向现存的对象来继承该对象。对象的
__proto__
指向自己构造函数的prototype
。obj.__proto__.__proto__...
的原型链由此产生,包括我们的操作符instanceof
正是通过探测obj.__proto__.__proto__... === Constructor.prototype
来验证obj
是否是Constructor
的实例。回到开头的代码,
two = new Object()
中Object
是构造函数,所以two.__proto__
就是Object.prototype
。至于one
,ES规范定义对象字面量的原型就是Object.prototype
。更深一步的探讨
我们知道JS是单继承的,
Object.prototype
是原型链的顶端,所有对象从它继承了包括toString
等等方法和属性。Object
本身是构造函数,继承了Function.prototype
;Function
也是对象,继承了Object.prototype
。这里就有一个_鸡和蛋_的问题:什么情况下会出现鸡和蛋的问题呢?就是声明一个包含所有集合的集合啊!好了,你们知道这是罗素悖论,但并不妨碍PL中这样设计。
那么具体到JS,ES规范是怎么说的?
以上均翻译自http://www.ecma-international.org/ecma-262/5.1/#sec-15,_鸡和蛋_的问题就是这么出现和设计的:
Function
继承Function
本身,Function.prototype
继承Object.prototype
。一张图和总结
Update: 图片来自 mollypages.org
相信经过上面的详细阐述,这张图应该一目了然了。
Function.prototype
和Function.__proto__
都指向Function.prototype
,这就是鸡和蛋的问题怎么出现的。Object.prototype.__proto__ === null
,说明原型链到Object.prototype
终止。