详解JavaScript中的__proto__和prototype

 对于学习JavaScript来说,__proto__和prototype的区别是个绕不开的话题。本文就试图从它们的根本上说清楚它们是什么,又有什么区别,所以本文会从JavaScript的对象开始说起,以其能把本文的主题说透彻。

  一、Javascript的对象创建方法

  大家都知道,在JavaScript的世界中有一句话:万物皆对象。而js中的对象跟其他强类型的语言中的对象又有着天壤之别,我们无意讨论js中对象跟譬如C#、java这种强类型语言中的对象的区别,不过我们还是要说下js中对象创建的方法。

  js语言本身非常灵活(不严谨),它的对象也非常灵活,比如js允许动态的给对象添加属性。js创建对象的方式也是很多的,比如我们需要创建一个用来描述“人”的对象p,该对象具有name属性,age属性和say()方法。那么创建对象的手段至少有如下几种:

  1、使用Object创建  

var p = new Object();
p.name = "homes";
p.age = 32;
p.say = function(){
    console.log("hello");
}

  2、使用字面量方式  

var p = { name:"homes" , age:32 , say:function(){console.log("hello");}};

  3、使用构造函数  

var Person = function(){
    this.name = "homes";
    this.age = 32;
    this.say = function(){
    console.log("hello");
    }
}
var p = new Person();

  总之还有很多方法,比如使用原型来创建对象,使用拷贝方式,使用工厂方式,等等等。其实这些方式只是形式上有所变化而已。这里我们重点要讨论的是第3种,使用构造函数创建对象。

  二、双对象法则

  首先我们来观察使用构造函数创建对象的代码,var Person = function( ){ ......} ,由于JavaScript没有“类”的概念,其实这里的Person其实就是个函数;不过在js的世界里还有一句话:万物皆对象,所以函数也是个对象(是由js内置对象Function实例化出来的),那么以此得出Person是一个函数性质的对象。

  上面这句话要说明什么呢?

  重点来了,在JavaScript中,如果一个对象是函数,那么这个对象是由两部分构成的,一个是这个函数的定义部分,如果当这个函数被当做构造函数来用了,那么这第一部分就是这个构造函数。

  第二部分,是这个函数的原型对象。很多人在看到“原型对象”这个词的时候彻底懵逼了。我们必须把这个"原型"这个词说明白了。我第一次看到这个词“原型对象”的时候,我就想起了西游记,里边的妖怪最后都要现原形,难不成js的世界就是妖怪的世界?每个对象都有个“原形”?可以通过某种手段让一个js对象现原形?这真是大错特错了。这里的“原型对象”只不过是起个名字而已,我们就这样理解就好了,每一个函数都是对象,当代码运行时,在内存中每个对象都由2块内容来合成这1个对象;换句话说代码运行时,内存里有2个独立的空间,这两个独立空间存储2个不同的对象,1个是函数体本身,一个是另外一个对象,这2个东西合成我们定义的一个函数对象,而这个“另一个对象”我们起名叫做“原型对象”,仅此而已。

  那接下来问题又来了。

  1、那个“原型对象”有什么用?

  2、既然一个函数对象是由2部分组成的,那这两部分又是如何联系起来的? 

  三、__proto__和prototype

  4.1  prototype是什么

  先不管那个额外的“原型对象”有什么鸟用,我们先看看一个函数对象的两部分是如何联系起来的。为什么要回答这个问题,因为如果两部分不能有效的联系起来,当我们js程序中有多个函数对象时岂不是要乱套?

  它们两部分之间是通过prototype联系起来的。先看图

  

  如图,我们上边声明了Person构造函数,当程序运行时,内存里会有2个部分,一个是左边的方框,代表该函数体,右边的方框代表该函数的另一部分,即它的“原型对象”。function的内存中,除了有我们声明name,age属性,还有一个prototype,这个属性是一个指针,它指向自己的原型对象所在地址。同理,每个原型对象也有一个属性,叫constructor,也是个指针,它指回了自己的另一半,即该原型所从属的构造函数。

  比如,我们可以使用如下语句:Person.prototype,来访问函数自己的原型对象。

  需要注意的是,如果这个对象不是函数,那么该对象不具有prototype属性。例如:

var p = { name:"homes" , age:32 , say:function(){console.log("hello");}};
console.log(p.prototype); //输出:undefined

  即使一个对象是根据构造函数实例化出来的,由于该对象不是函数,它也不具有prototype属性。例如:  

var Person = function(){
    this.name = "homes";
    this.age = 32;
    this.say = function(){
    console.log("hello");
    }
}
var p = new Person();
console.log(p.prototype); //输出:undefined

  说白了,prototype属性是只能通过     函数名.prototype     的形式来访问。

  4.2  __proto__又是什么鬼?

  首先要注意,这个“__proto__”左右两边是2个连续的下划线。

  它是JavaScript中对象的固有属性,也就是任何对象都有,既然如此,函数也是个对象,那么它也必须有,所以上图可以再改改,把这个__proto__加上去。

  

   我们看到,不管是函数本身那个框框,还是它的原型对象那个框框,都有“__proto__”属性,该属性也是一个指针,它指向“它的构造函数的原型对象”。怎么理解这句话?

  在js的世界里,每一个函数都是Function对象的实例,也就是通过new Function()构造出来的。所以在这里,Person函数也是由Function构造出来的一个实例,函数实例。所以,Person函数的__proto__指向了Function函数的原型对象,也就是Function.prototype。

  而Person的原型对象Person.prototype也是个对象,它也有__proto__属性,它的__proto__也要指向“它的构造函数的原型对象”,那么这个Person的原型对象的构造函数的原型对象又是谁呢?答案是Object的原型对象,也就是Object.prototype。

  接下来深入一步,既然Function的原型对象 Function.prototype是个对象,它也有__proto__属性吧,那Function.prototype.__proto__又指向谁呢?答案是指向

Object的原型对象,也就是Object.prototype。

  接下来再深入一步,Object.prototype的__proto__又指向谁呢?由于Object对象就是JavaScript的世界中的原始天尊了,所以Object.prototype.__proto__指向null。

  好绕啊,我决定画个图。

  

   请仔细观察此图,这个图里有一个非常诡异的浅蓝色箭头,它描述的信息是Object()的__proto__指针指向了Function.prototype。这是几个意思?你刚刚不是说了Object就相当于JavaScript的原始天尊了吗?额,额,好吧,Function对象是原始天尊的爸爸级别的。换句话说Object也是Function搞出来的。。。。

  4.3  那根据构造函数Person( ) 创建出来的实例呢?

  例如 :  var  p = new Person( );

  由Person()函数构造出来的实例p就只是个普通对象了,所以p不是函数,也就是说,p这个对象只有__proto__属性,没有prototype属性。p.__proto__指向“它的构造函数的原型对象”,所以p.__proto__指向的对象是Person.prototype,因为p的构造函数是Person,它的原型对象就是Person.prototype。如图。

  

  如图,虚线表示实例化的意思,p是由Person()构造函数实例化出来的。由于p不是函数,所以p没有它自己的"原型对象"。

  总结:

  1.每个对象都有"__proto__"属性,它指向该对象的构造函数的原型对象。

  2.函数是一种特殊的对象,它由2部分构成,一部分是函数体本身,一部分是它的源性对象

  3.函数除了有属性__proto__,还有属性prototype,prototype指向该函数的原型对象。

  到这里我们把__proto__和prototype基本说明白了,那么最后又引申出一个问题:这个该死的原型对象有什么用?我们留作下次分析吧

posted @ 2018-07-28 18:17  天天向上吧  阅读(715)  评论(0编辑  收藏  举报