原型继承补充(prototype和__proto__详解)

在上篇文章中,因为篇幅的缘由只是针对构造函数的构造过程和原型链的存取进行深刻的讲解,有点偏原理性的讲解,并无对___proto___prototypeconstructor这些属性之间的互相关系以及实际上的应用分析清楚。因此本文的目的就是为了加深对原型继承的理解,并可以将其应用在实际上。javascript

prototype

//建立一个构造函数。
function Fruit(){};
//输出其原型对象:
console.log(Fruit.prototype);复制代码

Fruit.prototype.png

//若是手动设置其prototype属性的话,那么将改变其原型对象
Fruit.prototype = {
    getName : function(){
        return this.name;
    },
    name : 'fruit',
};
//再次输出其原型对象,这时候就发生了点奇怪的事情了
console.log(Fruit.prototype);复制代码

Fruit.prototype1.png

对比两张图片,有没有发现异常,心细的人可能发现了 Fruit.prototype少了一个 constructor属性,那么 Fruit.prototype.constructor属性到底跑哪去了,咱们在控制台输出一下看看。

console.log(Fruit.prototype.constructor)
//function Object() { [native code] }
//可能有人会感到奇怪,在Fruit.prototype中明明并无该属性,那么这该死的constructor属性从哪里来的。
//咱们回到Fruit.prototype属性,点开__proto__属性,那么一切就明朗了。复制代码

Fruit.prototype1.__proto__.png

Fruit.prototype中,只有其的 __proto__拥有 constructor属性,因此是否是能够认为其 Fruit.prototype.constructor===Fruit.prototype.__proto__.constructor?事实上,咱们能够认为两者指向同一个构造函数。因为重写了 Fruit的原型对象,JavaScript引擎不能在显式原型中找到 constructor属性,那么它将经过隐式原型链查找,找到了 Fruit.prototype.__proto__的constructor属性。若是重写原型就会致使 constructor属性的更改,那么在实际开发的时候就会发生指向不明的错误,以下所示:

function Fruit(){}
function Animal(){}
Animal.prototype = new Fruit();
var apple = new Fruit();
var cat = new Animal();
alert(apple.constructor===cat.constructor);//true
//apple和cat明明属于两个不一样构造器产生的实例,可是它们的constructor属性指向同一构造器产生的实例复制代码

因此在修改构造函数的原型时候,应该修正该原型对象的constructor属性,一般修正方法有两种:java

//第一种方法:当其原型修改时,手动更改其原型的```constructor```属性的指向。
Animal.prototype.constructor = Animal;
//第二种方法:保持原型的构造器属性,在子类构造器函数内初始化实例的构造器属性,
function Animal(){
    this.constructor = arguments.callee;
    //或者能够:this.constructor = Animal;
}复制代码

在网上对constructor属性的做用有着许多不一样的见解,有的人认为其是为了将实例的构造器的原型对象更好的暴露出来,可是我我的认为constructor属性在整个原型继承中实际上是没有起到什么做用的,甚至在JS语言中也是如此,由于其可读写,因此其未必指向对象的构造函数,像上面的保持原型构造属性不变,只是从编程的习惯出发,让对象的constructor属性指向其构造函数。编程

说完了构造函数的prototype属性,因为我在上文就已经介绍过了普通的函数与构造函数并无什么本质的区别,因此如今咱们开始将目光放在一些特殊的函数上面。app

Function是JavaScript一个特殊的构造函数,在JS中,每个函数都是其对象(Object也是)。在控制台输出下Function.prototype获得这样一个函数function () { [native code] }。再用函数

console.log(Function.prototype);
//function () { [native code] }
//用typeof判断下其类型
console.log(typeof Function.prototype)//function
//既然其是function类型的,那么由于全部的函数都有prototype对
//象,因此其确定就有prototype属性了,那么咱们如今能够输出看看了,可是神奇的事情发生了。
console.log(Function.prototype.prototype)//undefined
//其竟然输出了undefined,这发生了什么事情??复制代码

翻阅了许多资料,终于让我找到了其缘由所在。而这与JavaScript的底层有关了。在上篇文章,咱们就说到了Object.prototype处于原型链的顶端,而JavaScript在Object.prototype的基础上又产生了一个Function.prototype对象,这个对象又被称为[Function:Empty](空函数,其是一个不一样于通常函数的函数对象)。随后又以该对象为基础打造了两个构造函数,一个即为Function,另外一个为Object。意不意外,惊不惊喜!可是看到下面,你又会刚到更加意外的。因此,在下面的代码如此显示,你就不会感到意外了。ui

console.log(Object.__proto__ === Function.prototype);//true
//Object的__proto__属性指向Function.prototype。这又说明Object这个构造器是从Function的原型生产出来的。
console.log(Object.constructor === Function);//true
//Object.constructor属性指向了它的构造函数Function
//看着上面的代码,是否是可以得出Object是一个Function的实例对象的结论。复制代码

没错,Object这个构造函数是Function的一个实例(由于Object是继承自Function.prototype,甚至能够这样说,全部的构造函数都是 Function的一个实例。this

__proto__

谈完了prototype属性,如今咱们开始来看看__proto__属性,在上篇文章中,咱们就已经提到了__proto__指向的是当前对象的原型对象。因为在JS内部,__proto__属性是为了保持子类与父类的一致性,因此在对象初始化的时候,在其内部生成该属性,并拒绝用户去修改该属性。尽管目前咱们能够手动去修改该属性,可是为了保持这种一致性,尽可能不要去修改该属性。废话很少说,咱们来看看一些示例:spa

//一个普通的函数
function Fruit(){};
console.log(Fruit.__proto__);//function(){ [native code] }
//貌似有点眼熟,像是上面的空函数,动手试试
console.log(Fruit.__proto__===Function.prototype)//true
//恩,有点大惊小怪了,对象的__proto__就是指向构造该对象的构造函数的原型对象。
//若是两者不等的话,那就出事了。
//如今来看看一个构造函数构造出来的对象
var apple = new Fruit();
console.log(apple.__proto__);
//其指向了Fruit.prototype,可是若是Fruit.prototype该变量,那会怎么样呢?
Fruit.prototype = {};
console.log(apple.__proto__);
//貌似跟上面并无多大的变化,可是别急,咱们接下来看。
var banana = new Fruit();
console.log(banana.__proto__);
//{};这就对了,对象的__proto__就是指向原型对象的,当构造函数的原型对象改变的时候,其也将改变。
//至于为何apple和banana的__proto__属性会变化,这就涉及到内存分配的问题了,在这里就再也不展开。复制代码

因为每一个对象都将拥有一个__proto__属性,那么apple.__proto__必然拥有__proto__属性,那就让咱们一块儿探究下吧。prototype

function Animal(){};
var dog = new Animal();
console.log(dog.__proto__.__proto__)
//Object {__defineGetter__: function, __defineSetter__: function, hasOwnProperty: function, __lookupGetter__: function, __lookupSetter__: function…}
//是否是很眼熟,这跟上面的Object.prototypey如出一辙,输出看看
console.log(dog.__proto__.__proto__==Object.prototype) //true复制代码

其实仔细分析下就应该知道这样的指向,dog.__proto__指向Animal.prototype,而Animal.prototype实际上是一个对象实例,由Object所构造出来的,天然Animal.prototype.__proto__指向Object.prototype。看完了对象的__proto__属性,如今来看下函数的相关属性。3d

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

可能有人会对Animal.__proto__.__proto__.__proto__===null产生疑惑,有人也是由于这样而认为在整个原型链的顶端就是null,其实否则,由于null压根就没有任何属性,天然对象和函数就不能从中继承到什么东西了。
其实在JavaScript内部,当实例化一个对象的时候,实例对象的__proto__指向了构造函数的prototype属性,以此来继承构造函数prototype上全部属性和方法。

总结:其实若是可以缕清__proto__prototype两者的关系,那么关于原型继承就很简单了。每一个对象都拥有了__proto__属性,全部对象的__proto__属性串联起了一条原型链,链接了拥有继承关系的对象,这条原型链的终点指向了Object.prototype

相关文章
相关标签/搜索