原型链

原型链是一种机制,指的是JavaScript每个对象包括原型对象都有一个内置的[[proto]]属性指向创建它的函数对象的原型对象,即prototype属性。原型链的存在,主要是为了实现对象的继承

函数即对象

  • js分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性
  • Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String

__proto__

JavaScript在创建对象的时候,都会有一个[[proto]]的内置属性,用于指向创建它的函数对象的prototype。原型对象也有[[proto]]属性。因此在不断的指向中,形成了原型链。

prototype原型对象

当定义一个函数对象的时候,会包含一个预定义的属性,叫prototype,这就属性称之为原型对象。
只有函数有prototype属性,也就是说__proto__ 的值是它所对应的原型对象,是某个函数的prototype

constructor

实例的constructor属性指向它的构造函数
原型对象prototype上的预定义的constructor属性,用来引用它的函数对象。这是一种循环引用。
经常会有下列的写法,加constructor是因为重写了原型对象,constructor属性就消失了,需要自己手动补上。

function F(){};
F.prototype = {
    constructor : F,
    doSomething : function(){}
}

JavaScript在创建对象的时候,都会有一个[[proto]]的内置属性,用于指向创建它的函数对象的prototype。原型对象也有[[proto]]属性。因此在不断的指向中,形成了原型链。

原型链的内存结构

原型链终极探索图

注意几个指向问题,下文详细分析

  • Foo.prototype和Function.prototype指向Object.prototype
  • Foo()的__proto__指向Function.prototype
  • 别的函数对象constructor指向Function,Function的constructor指向自身

普通构造函数的实例对象原型链分析

这里的myfun()相当于上图的Foo()函数

    function myfun(x,y){
      this.x = x
      this.y = y  
      this.sum = function(){
        return x+y
      }
    }

    myfun.prototype.sum = function(){
      return this.x+this.y
    }
    myobj = new myfun(1,2)
    console.log(myobj)

非Function类型函数对象的原型链分析

常用到的Array()、RegExp()、Date()、Boolean()、Number()、String()这些构造函数其实都是有Function构造而来,从他们的constructor属性的指向可以证明,这里我分析一下Array()的原型链.
可以看到Array._proto_是一个function,也就是说Array._proto_指向的Function.prototype是一个function,其他普通对象的原型却是一个Object
Array.__proto__ -> Function.prototype
Function.prototype是一个函数类型的对象
Function.prototype.__proto__指向最顶层的Object.prototype

console.log(Array)

Function函数对象的原型链

在ES6标准中,Function 对象有两个属性:

  • length 值为1,这个属性的特性为{ [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true },即不可覆盖,不可被for...in遍历,但可以通过Object.defineProperty修改它的上面这些特性
  • prototype 为原型对象,(见ES最新标准说明 Function.prototpye)它跟一般函数的prototype的区别在于

    • 它不可写,不可配置,不可遍历。 即它永远指向固定的一个对象,且是其他所有函数的原型对象,所有函数本身的__proto__指向它。
    • 它是一个函数。 一般函数的prototype是一个带有constructor属性的普通对象,但Functionprototype是一个函数对象(built-in function object),js中唯一一个默认prototype为函数的对象

函数和对象都有__proto__属性,指向构造函数的prototype属性所指向的对象,即它的原型对象。
而函数的__proto__属性(并非它的原型对象prototype上的__proto__属性)指向Function.prototype,所以Function.prototype上的属性和方法都会被函数对象(function object)所继承。

似乎进入了“鸡生蛋和蛋生鸡”的哲学范畴
Object本身是构造函数,继承了Function.prototype;
Function也是对象,继承了Object.prototype

Function.__proto__ === Function.prototype // true 
Object.__proto__ === Function.prototype // true
Object.prototype.__proto__ === null // true
Function.prototype.__proto__ === Object.prototype // true
Object.prototype === Object.__proto__ // false
Object instanceof Function // true
Function instanceof Object // true

完全没必要去纠结鸡生蛋还是蛋生鸡的问题,之所以Function.__proto__ === Function.prototype,是为了表明Function作为一个原生构造函数,本身也是一个函数对象(由自己构造出的实例),让 Function这个构造函数 可以获取定义在 Object.prototype 上的方法。

console.log(Function)

总结

属性



万物皆对象

注:全部为自己的看法,可能与标准的说法有出入,只是方便自己理解,我快晕了
js里万物皆为对象,最顶层为Object对象是一切的原型(起源) 注意这里的Object不是构造函Object()是一个切实的原型对象
Object(),Function(),Array(),String()这些方法就像我们自定义的方法一样(只不过是js语言帮我们内置创建了这些方法来创建特殊的类,本质都是Object)
每个方法都有(可以说是创建了)自己的prototype(原型对象),然后这些原型对象的_proto_又都指向了最顶层的Object原型对象(由此可以说明是顶层对象创建了每个方法的原型对象,也就是说方法类的原型对象是顶层Object类的原型对象创建的实例一样)
其中的Function方法较为特殊,Object(),Array(),String()这些方法都是有Function方法构造而来
Function构造函数本身就是Function的实例(像是自己构造自己得到的函数对象),所以_proto_指向Function的原型对象.

再看一张原型链的铁三角图

注意:一个函数存在两种状态

  • 作为构造函数时,可以去new一个实例对象,这个对象可以是普通对象也可以说函数对象(Function就可以构造函数对象)
  • 作为被构造的函数(实例)对象时,自己的__proto__要指向构造自己的构造函数的prototype(就是指向原型对象)

参考文章

https://juejin.im/post/5cc99fdfe51d453b440236c3
https://segmentfault.com/a/1190000011880268
https://segmentfault.com/a/1190000005363885
https://juejin.im/post/5b3798f851882574c105c51c
https://github.com/creeperyang/blog/issues/9


ipromise
584 声望25 粉丝

xxxxxx