从prototype与__proto__窥探JS继承之源 | 掘金技术征文

以前分享了一篇文章JS原型链与继承别再被问倒了,发现继承的问题研究到后面,必定会触及到Object.prototype和Function.prototype这些概念,为了解答疑惑,本篇就抽丝剥茧,从prototype与__proto__来推出函数与对象的深层关系。
原文:详解prototype与__proto__html

概念

  1. prototype 是函数(function) 的一个属性, 它指向函数的原型.
  2. __proto__ 是对象的内部属性, 它指向构造器的原型, 对象依赖它进行原型链查询,instanceof 也是依赖它来判断是否继承关系.

由上, prototype 只有函数才有, 其余(非函数)对象不具备该属性. 而 __proto__ 是对象的内部属性, 任何对象都拥有该属性. git

栗子

下面咱们来吃个栗子帮助消化下:github

function Person(name){
  this.name = name;
}
var p1 = new Person('louis');

console.log(Person.prototype);//Person原型 {constructor: Person(name),__proto__: Object}
console.log(p1.prototype);//undefined

console.log(Person.__proto__);//空函数, function(){}
console.log(p1.__proto__ == Person.prototype);//true复制代码

吃栗子时咱们发现, Person.prototype(原型) 默认拥有两个属性:浏览器

  • constructor 属性, 指向构造器, 即Person自己
  • __proto__ 属性, 指向一个空的Object 对象

而p1做为非函数对象, 天然就没有 prototype 属性; 此处佐证了概念1app

下面来看看__proto__属性:函数

Person.__proto__ 属性 指向的是一个空函数( function(){} ), 待会儿咱们再来研究这个空函数.post

p1.__proto__ 属性 指向的是 构造器(Person) 的原型, 即 Person.prototype. 此处佐证了概念2测试

这里咱们发现: 原型链查询时, 正是经过这个属性(__proto__) 连接到构造器的原型, 从而实现查询的层层深刻.ui

概念1 不太理解的同窗, 说明大家不会吃栗子, 我们忽略他们. 对 概念2 不太理解的同窗, 咱们来多吃几个栗子, 边吃边想:this

var obj = {name: 'jack'},
    arr = [1,2,3],
    reg = /hello/g,
    date = new Date,
    err = new Error('exception');

console.log(obj.__proto__  === Object.prototype); // true
console.log(arr.__proto__  === Array.prototype);  // true
console.log(reg.__proto__  === RegExp.prototype); // true
console.log(date.__proto__ === Date.prototype);   // true
console.log(err.__proto__  === Error.prototype);  // true复制代码

可见, 以上经过 对象字面量 和 new + JS引擎内置构造器() 建立的对象, 无一例外, 它们的__proto__ 属性所有指向构造器的原型(prototype). 充分佐证了 概念2 .

__proto__

刚才留下了一个问题: Person.__proto__ 指向的是一个空函数, 下面咱们来看看这个空函数到底是什么.

console.log(Person.__proto__ === Function.prototype);//true复制代码

Person 是构造器也是函数(function), Person的__proto__ 属性天然就指向 函数(function)的原型, 即 Function.prototype.

这说明了什么呢?

咱们由 "特殊" 联想到 "通用" , 由Person构造器联想通常的构造器.

这说明 全部的构造器都继承于Function.prototype (此处咱们只是由特殊总结出了普适规律, 并无给出证实, 请耐心看到后面) , 甚至包括根构造器Object及Function自身。全部构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind(ES5)等. 以下:

console.log(Number.__proto__   === Function.prototype); // true
console.log(Boolean.__proto__  === Function.prototype); // true
console.log(String.__proto__   === Function.prototype); // true
console.log(Object.__proto__   === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Array.__proto__    === Function.prototype); // true
console.log(RegExp.__proto__   === Function.prototype); // true
console.log(Error.__proto__    === Function.prototype); // true
console.log(Date.__proto__     === Function.prototype); // true复制代码

JavaScript中有内置(build-in)构造器/对象共计13个(ES5中新加了JSON),这里列举了可访问的9个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎建立,Math,JSON是以对象形式存在的,无需new。因为任何对象都拥有 __proto__ 属性指向构造器的原型. 即它们的 __proto__ 指向Object对象的原型(Object.prototype)。以下所示:

console.log(Math.__proto__ === Object.prototype);  // true
console.log(JSON.__proto__ === Object.prototype);  // true复制代码

如上所述, 既然全部的构造器都来自于Function.prototype, 那么Function.prototype 究竟是什么呢?

Function.prototype

咱们借用 typeof 运算符来看看它的类型.

console.log(typeof Function.prototype) // "function"复制代码

实际上, Function.prototype也是惟一一个typeof XXX.prototype为 “function”的prototype。其它的构造器的prototype都是一个对象。以下:

console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Object.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object复制代码

JS中函数是一等公民

既然Function.prototype 的类型是函数, 那么它会拥有 __proto__ 属性吗, Function.prototype.__proto__ 会指向哪里呢? 会指向对象的原型吗? 请看下方:

console.log(Function.prototype.__proto__ === Object.prototype) // true复制代码

透过上方代码, 且咱们了解到: Function.prototype 的类型是函数, 也就意味着一个函数拥有 __proto__ 属性, 而且该属性指向了对象(Object)构造器的原型. 这意味着啥?

根据咱们在 概念2 中了解到的: __proto__ 是对象的内部属性, 它指向构造器的原型.

这意味着 Function.prototype 函数 拥有了一个对象的内部属性, 而且该属性还刚好指向对象构造器的原型. 它是一个对象吗? 是的, 它必定是对象. 它必须是.

实际上, JavaScript的世界观里, 函数也是对象, 函数是一等公民.

这说明全部的构造器既是函数也是一个普通JS对象,能够给构造器添加/删除属性等。同时它也继承了Object.prototype上的全部方法:toString、valueOf、hasOwnProperty等。

Object.prototype

函数的 __proto__ 属性指向 Function.prototype, 如: Person.__proto__ —> Function.prototype

Function.prototype 函数的 __proto__ 属性指向 Object.prototype, 如: Function.prototype.__proto__ —> Object.prototype.

那么Object.prototype.__proto__ 指向什么呢?

console.log(Object.prototype.__proto__ === null);//true复制代码

因为已经到顶, JS世界的源头一片荒芜, 居然什么都没有! 使人嗟叹不已.

都说一图胜千言, 我也不能免俗. 下面附一张 stackoverflow 上的图:

这张图也指出:

  • Object.__proto__ == Function.prototype,
  • Function.__proto__ == Function.prototype.
//虽然上面作过测试, 咱们仍是再次测试下
console.log(Object.__proto__   == Function.prototype);//true
console.log(Function.__proto__ == Function.prototype);//true复制代码

因为对象构造器 (Object) 也是构造器, 又构造器都是函数, 又函数是一等公民, 函数也是对象.

故, 对象构造器 (Object) 拥有3种身份:

  • 构造器
  • 函数
  • 对象

推而广之, 全部构造器都拥有上述3种身份.

因为构造器是 对象 (身份3), 理所固然拥有 __proto__ 属性, 且该属性必定指向其构造器的原型, 也就是指向 函数 (身份2) 构造器(Function)的原型, 即 Function.prototype. 因而咱们证实了上面那句 全部的构造器都继承于Function.prototype (身份1).

注: 上面代码中用到的 __proto__ 目前在IE6/7/8/9中并不支持。IE9中可使用Object.getPrototypeOf(ES5)获取对象的内部原型。


附上网友的疑问(问题提得特别好,问出了函数与对象最尖锐的归宿问题):

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

如下是具体问题:

  1. 先有 Object.prototype,再有 Object,那么先有 Object.prototype 里面的这个 Object 表明的是什么呢?

  2. Function.__proto__=== Function.prototype;
    Function 构造函数的 prototype 属性和__proto__属性都指向同一个原型,是否能够说 Function 对象是由 Function 构造函数建立的一个实例?

  3. Object instanceof Function // true
    Function instanceof Object // true
    Object 自己是构造函数,继承了 Function.prototype; Function 也是对象,继承了Object.prototype。感受成了鸡和蛋的问题了。

  4. 好比说:
    function Person(){}
    Person.prototype 是一个对象,为何 Function.prototype 倒是一个函数呢,固然函数也是一个对象,为何要这么设计呢?

这里是疑惑:

感受有些地方很难理解,总感受有种悖论同样,还有人扯到罗素悖论,各有说辞,不知道楼主怎么看。。。

通读全文若是你能回答这些问题,说明看懂了本文。不然请容许我建议你再读一篇,别打我。


---华丽丽的分割线---

如下是回答:
答1: 先有的必定是Object,它是BOM对象,打印出来为:function Object() { [native code] },而后才是Object.prototype。这不矛盾,请看分析。
首先:Object.create(null)你应该知道,它能够建立一个没有原型的对象。以下:
Object.prototype.__proto__ == Object.create(null).__proto__
结果为true,也就是说,Object.prototype是使用这种方式生成的,而后才绑在Object的prototype属性上。为何这里没有用===,由于__proto__指向的是对象,对象是无法比较相等的。
要知道,对象能够先生成,而后再赋予新的属性。

答2: Function.__proto__=== Function.prototype 这没毛病。
咱们都知道,Function是一个函数,只要是函数,它的__proto__就指向 Funtion.prototype. 这是继承的设定。那么咱们怎么理解这种构造器自己就继承自己的prototype属性的现象呢?
Function生成时,是没有__proto__属性的,它是一个BOM对象,打印出来为:function Function() { [native code] },Function.prototype一样是BOM对象,打印出来为:function () { [native code] },那么能够这么理解:
Function的__proto__和prototype属性都是后面才指向同一个BOM对象的。

答3: 这不是蛋和鸡的问题,计算机中没有蛋和鸡。
Object instanceof Function // true 。Object是构造器函数,你必须认,有new Object()为证。
答2已经给告终论:只要是函数,它的__proto__就指向 Funtion.prototype。Object也是函数。所以它的__proto__属性会指向Funtion.prototype。故而Object instanceof Function 为 true。

答4: Function.prototype是一个函数,也是对象。这是天然的。
那么为何这么设计呢?你注意到没有 typeof Function.prototype === "function" 结果为true。注意:只有Function的prototype是函数,其余都是普通对象。它是一个桥梁,咱们一直说函数也是对象,只有这个知足了, 函数才能是对象,关于『js中函数是一等公民』的定理,Function.prototype同时是函数, 又是原型对象即是佐证。

说了说去,都回到了Function.prototype、Function和Object的问题上,本质上它们都是BOM对象,即它们都在浏览器global对象(这个js是访问不到的)生成以前生成的,后面的指向关系,能够认为是js世界继承规律的一种设定,有了这个设定,js游戏才能玩下去。


本文就讨论这么多内容,你们有什么问题或好的想法欢迎在下方参与留言和评论.

本文做者: louis

本文连接: louiszhai.github.io/2015/12/17/…

本次活动连接:juejin.im/post/58d8e9…

参考文章

相关文章
相关标签/搜索