Skip to content

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

Open
@creeperyang

Description

@creeperyang
Owner

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

不过不是开玩笑,本文的确打算从__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终止。

Activity

creeperyang

creeperyang commented on Aug 13, 2015

@creeperyang
OwnerAuthor

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

showtestlog commented on Nov 3, 2015

@showtestlog

十分感谢

Kelichao

Kelichao commented on Apr 29, 2016

@Kelichao

会玩!

creeperyang

creeperyang commented on Apr 29, 2016

@creeperyang
OwnerAuthor

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

zhuanna

zhuanna commented on May 4, 2016

@zhuanna

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

For-me

For-me commented on Aug 31, 2016

@For-me

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

SiZapPaaiGwat

SiZapPaaiGwat commented on Sep 18, 2016

@SiZapPaaiGwat

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

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

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

stkevintan commented on Mar 2, 2017

@stkevintan

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

WesleyQ5233

WesleyQ5233 commented on Mar 30, 2017

@WesleyQ5233

get

96 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @deqing@whosesmile@mystorp@SiZapPaaiGwat@yangblink

        Issue actions

          从__proto__和prototype来深入理解JS对象和原型链 · Issue #9 · creeperyang/blog