Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从__proto__和prototype来深入理解JS对象和原型链 #9

Open
creeperyang opened this issue Aug 13, 2015 · 76 comments
Open

从__proto__和prototype来深入理解JS对象和原型链 #9

creeperyang opened this issue Aug 13, 2015 · 76 comments

Comments

@creeperyang
Copy link
Owner

creeperyang commented Aug 13, 2015

就标题而言,这是七八篇里起得最满意的,高大上,即使外行人也会不明觉厉! 😂

不过不是开玩笑,本文的确打算从__proto__prototype这两个容易混淆来理解JS的终极命题之一:对象与原型链

__proto__prototype

__proto__

引用《JavaScript权威指南》的一段描述:

Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.

翻译出来就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。好啦,既然有这么一个原型对象,那么对象怎么和它对应的?

对象__proto__属性的值就是它所对应的原型对象:

var one = {x: 1};
var two = new Object();
one.__proto__ === Object.prototype // true
two.__proto__ === Object.prototype // true
one.toString === one.__proto__.toString // true

上面的代码应该已经足够解释清楚__proto__了:grin:。好吧,显然还不够,或者说带来了新的问题:Object.prototype是什么?凭什么说onetwo的原型就是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__指向自己构造函数的prototypeobj.__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。这里就有一个_鸡和蛋_的问题:

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

什么情况下会出现鸡和蛋的问题呢?就是声明一个包含所有集合的集合啊!好了,你们知道这是罗素悖论,但并不妨碍PL中这样设计。

那么具体到JS,ES规范是怎么说的?

Function本身就是函数Function.__proto__是标准的内置对象Function.prototype

Function.prototype.__proto__是标准的内置对象Object.prototype

以上均翻译自http://www.ecma-international.org/ecma-262/5.1/#sec-15,_鸡和蛋_的问题就是这么出现和设计的:**`Function`继承`Function`本身,`Function.prototype`继承`Object.prototype`。**

一张图和总结

原型链

Update: 图片来自 mollypages.org

相信经过上面的详细阐述,这张图应该一目了然了。

  1. Function.prototypeFunction.__proto__都指向Function.prototype,这就是鸡和蛋的问题怎么出现的。
  2. Object.prototype.__proto__ === null,说明原型链到Object.prototype终止。
@creeperyang
Copy link
Owner Author

creeperyang commented Aug 13, 2015

ObjectFunction的鸡和蛋的问题

ES5关于ObjectFunction的规定:

Object

Function

从上面的规定再结合其它,理出以下几点:

  1. 原型链的尽头(root)是Object.prototype所有对象均从Object.prototype继承属性。

    prototype
  2. Function.prototypeFunction.__proto__同一对象

    function prototype

    这意味着: Object/Array/String等等构造函数本质上和Function一样,均继承于Function.prototype

  3. Function.prototype直接继承root(Object.prototype)。

    function object

    通过这点我们可以弄清 继承的原型链:Object.prototype(root)<---Function.prototype<---Function|Object|Array... 如下图所示:

    chain

以上3点比较容易理解,或者说规范里就这样定义的。由以上3点导出我们最后的问题:ObjectFunction的鸡和蛋的问题。

回答这个问题,必须首先更深入一层去理解Function.prototype这个对象,因为它是导致Function instanceof ObjectObject instanceof Function都为true的原因。

回归规范,摘录2点:

  1. 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.

    1. Function.prototype像普通函数一样可以调用,但总是返回undefined
    2. 普通函数实际上是Function的实例,即普通函数继承于Function.prototypefunc.__proto__ === Function.prototype
    3. Function.prototype继承于Object.prototype,并且没有prototype这个属性。func.prototype是普通对象,Function.prototype.prototypenull
    4. 所以,Function.prototype其实是个另类的函数,可以独立于/先于Function产生。
  2. 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而产生,最后,FunctionObject和其它构造函数继承Function.prototype而产生。

@showtestlog
Copy link

十分感谢

@Kelichao
Copy link

会玩!

@creeperyang
Copy link
Owner Author

creeperyang commented Apr 29, 2016

@Kelichao 哈哈,欢迎提出见解。

@zhuanna
Copy link

zhuanna commented May 4, 2016

相当绕啊,不过终于让我明白透了这关系。

@For-me
Copy link

For-me commented Aug 31, 2016

再次观摩男神。。再学习一遍

@SiZapPaaiGwat
Copy link

这东西感觉要隔一段时间就看,不然很容易被绕迷糊。
感觉把Object.prototype和Function.prototype这两看成浏览器的私货,比如下面这操蛋的一对:

typeof Object.prototype === 'object' && (Object.prototype instanceof Object === false)

typeof Function.prototype === 'function' && (Function.prototype instanceof Function === false)

@stkevintan
Copy link

@simongfxu typeof 这个操作符跟原型链没什么必然的关系. typeof null 还等于 'object'呢.

@WesleyQ5233
Copy link

get

@igblee
Copy link

igblee commented Apr 9, 2017

为什么我试了一下第一个例子都是返回false,用的是chrome。

@creeperyang
Copy link
Owner Author

@igblee 截图看看

@whcxxb
Copy link

whcxxb commented Apr 19, 2017

学习一下

@Evllis
Copy link

Evllis commented Apr 19, 2017

我喜欢你画的图,好有感觉。么么哒

@jawil
Copy link

jawil commented Apr 19, 2017

又被骗进来了吧😄 @Evllis

@creeperyang
Copy link
Owner Author

creeperyang commented Apr 19, 2017

@Evllis 忘记从哪偷的图了... 很久很久以前就有这张图了。

Update: 找不到图片具体哪来的,但目前看起来,一个可信的来源是 mollypages.org

@Cacaci
Copy link

Cacaci commented Apr 19, 2017

图是从王福朋那里来的 他写的一系列关于原型链的文章,大家伙可以去看看

@jawil
Copy link

jawil commented Apr 19, 2017

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的问题。

@creeperyang
Copy link
Owner Author

creeperyang commented Apr 19, 2017

其实我们可以先看这样一个问题:

Object.prototype 是对象吗?

  1. 当然是。An object is a collection of properties and has a single prototype object. The prototype may be the null value. 这是object的定义,Object.prototype显然是符合这个定义的。
  2. 但是,Object.prototype并不是Object的实例。 这也很好理解Object.prototype.__proto__null

2017-04-19 8 11 34

这已经某种程度上解开了鸡和蛋的问题:Object.prototype是对象,但它不是通过Object函数创建的。

下面我们来看Function/Function.prototype

2017-04-19 8 20 59

似乎可以看出一点东西:

在chrome的console中,Array.prototype以数组的形式输出,Map.prototype以Map的形式输出,Function.prototype输出function () { [native code] }... 可以反过来讲继承了某个prototype,console就认为是对应的类型(以prototype来判断)。

2017-04-19 8 32 46

就上图而言,我们有疑惑的其实就是为什么 Function.prototypeFunction.__proto__是同一个对象

  1. Function本身也是function。
  2. Function.prototype是所有function的原型(包括Function自己)。
  3. 但反过来,Function.prototypeFunction并没有反向的什么关系(除了正向的Function继承了Function.prototype)。

所以疑惑就可以解除了:Function.prototypeFunction.__proto__相同不代表Function这个函数是由自身创建的。先有了Function.prototype这个对象(其实也是函数,下面说明),然后才有了其它函数而已。

那么问题来了,Function.prototype/Function.__proto__是 function 吗(对比开头的问题)?

  1. 当然是。比如我们可以正常执行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.

  2. 然而 Function.prototype 不是 Function 的实例。


下面附加一幅图帮助理解:

2017-04-19 9 17 51

@noobalex
Copy link

用issue写博客。。。。。这奇葩的方式也是没谁了,用github pages发布个静态博客有那么难?

@rccoder
Copy link

rccoder commented Apr 20, 2017

@eddiebai 这不是难不难的问题吧,issue 相比 github pages 更加方便吧...

@jawil
Copy link

jawil commented Apr 20, 2017

你们怎么都来得这么快?都睡在GayHub里面吗?@rccoder

@rccoder
Copy link

rccoder commented Apr 20, 2017

@jawil 邮件会 push 啊...

@creeperyang
Copy link
Owner Author

@waterVenice7当然没有搞错。一个最简单的例子:

const parent = { x: 1 };
const child = Object.create(parent);

child 继承了 parent的说法,显然是合理。

如果你有一些其它面向对象语言的基础,那么问出这个问题是比较自然的,因为对Java/C++之类的语言来说,类(class)和实例(instance)就是完全不同的两个概念。它通过类的继承来实现继承。

但是,对JavaScript而言,它没有类(class)和实例(instance)的区别,它只有对象(object)。它通过原型链来实现继承(指定某个对象的原型对象 __proto__)。

@EthanLin-TWer
Copy link

EthanLin-TWer commented Oct 24, 2018

最后总结:先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数继承Function.prototype而产生。

看到这里,关于鸡蛋问题我的唯一疑问就是,Object.prototype 显然也是个对象,而对象最终都必须由 Function 生成,但混沌之初,此时还并没有 Function。于是又往下看:

这已经某种程度上解开了鸡和蛋的问题:Object.prototype是对象,但它不是通过Object函数创建的。

验证了一下,果然是这样:

Object.prototype instanceof Object // false
Object.prototype instanceof Function // false

所以,大概回答就是,Object.prototype 是个神之对象,由它诞生了 Function.prototype,以之为原型又诞生了 FunctionObject,接着创造了对象世界的万物吧。

自荐几篇博客,记录了对原型这个技术问题的思考哟:

@zhoubhin
Copy link

只能说,在JS的世界里,先有Object.prototype后有天,然后有了Function.prototype,接着就有了Function,Object等等

@OrangeSAM
Copy link

写的非常好,理清了我的思路。

@relines
Copy link

relines commented May 29, 2019

所以鸡蛋的问题是通过规定解决的么:
Function.proto === Function.prototype,(Function是Function的实例);
Function.prototype.proto === Object.prototype,(Function.prototype是Object的实例);

@wangcansunking
Copy link

所以鸡蛋的问题是通过规定解决的么:
Function.proto === Function.prototype,(Function是Function的实例);
Function.prototype.proto === Object.prototype,(Function.prototype是Object的实例);

真实的世界不知道,但是计算机的世界回到最开始的地方就是通过规定解决的吧

@bardliu
Copy link

bardliu commented May 31, 2019

“Object和Function的鸡和蛋的问题”中的第2点:
Function.prototype和Function.__proto__为同一对象。出现以下情况: console.log(Function.prototype === Function.proto_); // false

你用Function.prototype 去和一个不存在的Function.proto去做比较,得出false,然后说博主有问题?并且说出 权威指南这本书很烂,还说博主用github写博文是为了吸星。
你是不是对编程有什么误解?博主说 Function.prototype === Function.**_ _ proto _ _ **
前后两个 _ _ 请你看清楚了。如果你得不到true, 麻烦你去查别的资料。

@Future23365
Copy link

原来这个原型链这么深奥。。

@songxc2020
Copy link

勘误:”Function也是对象,继承了Object.prototype“,应改为"Function.prototype也是对象,继承了Object.prototype"

@AntonSmir
Copy link

AntonSmir commented Feb 20, 2022 via email

@weihong1028
Copy link

weihong1028 commented Feb 20, 2022 via email

@chunfYan
Copy link

讲的太好啦!

@AntonSmir
Copy link

AntonSmir commented Dec 11, 2022 via email

@18boys
Copy link

18boys commented Dec 11, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests