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

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

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属性,通常修正方法有两种:

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

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

说完了构造函数的prototype属性,由于我在上文就已经介绍过了普通的函数与构造函数并没有什么本质的区别,所以现在我们开始将目光放在一些特殊的函数上面。

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。意不意外,惊不惊喜!但是看到下面,你又会刚到更加意外的。所以,在下面的代码如此显示,你就不会感到意外了。

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的一个实例。

__proto__

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

//一个普通的函数
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__属性,那就让我们一起探究下吧。

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__属性,现在来看下函数的相关属性。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容