内容简介:原型继承补充(prototype 和__proto__详解)
在上篇文章中,由于篇幅的原因只是针对构造函数的构造过程和原型链的存取进行深入的讲解,有点偏原理性的讲解,并没有对 ___proto___
、 prototype
和 constructor
这些属性之间的互相关系以及实际上的应用分析清楚。所以本文的目的就是为了加深对原型继承的理解,并能够将其应用在实际上。
prototype
//创建一个构造函数。 function Fruit(){}; //输出其原型对象: console.log(Fruit.prototype);
//如果手动设置其prototype属性的话,那么将改变其原型对象 Fruit.prototype = { getName : function(){ return this.name; }, name : 'fruit', }; //再次输出其原型对象,这时候就发生了点奇怪的事情了 console.log(Fruit.prototype);对比两张图片,有没有发现异常,心细的人可能发现了
Fruit.prototype
少了一个
constructor
属性,那么
Fruit.prototype.constructor
属性到底跑哪去了,我们在控制台输出一下看看。
console.log(Fruit.prototype.constructor) //function Object() { [native code] } //可能有人会感到奇怪,在Fruit.prototype中明明并没有该属性,那么这该死的constructor属性从哪里来的。 //我们回到Fruit.prototype属性,点开__proto__属性,那么一切就明朗了。在
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
。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【前端基础进阶】JS原型、原型链、对象详解
- 详解js原型,构造函数以及class之间的原型关系
- 原型继承补充(prototype 和__proto__详解)
- 详解原型链中的prototype和 __proto__
- 图解原型和原型链
- 创建对象、原型、原型链
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。