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

栏目: 编程语言 · 发布时间: 7年前

内容简介:原型继承补充(prototype 和__proto__详解)

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

prototype

//创建一个构造函数。
function Fruit(){};
//输出其原型对象:
console.log(Fruit.prototype);
原型继承补充(prototype 和__proto__详解)
Fruit.prototype.png
//如果手动设置其prototype属性的话,那么将改变其原型对象
Fruit.prototype = {
    getName : function(){
        return this.name;
    },
    name : 'fruit',
};
//再次输出其原型对象,这时候就发生了点奇怪的事情了
console.log(Fruit.prototype);
原型继承补充(prototype 和__proto__详解)
Fruit.prototype1.png
对比两张图片,有没有发现异常,心细的人可能发现了 Fruit.prototype 少了一个 constructor 属性,那么 Fruit.prototype.constructor

属性到底跑哪去了,我们在控制台输出一下看看。

console.log(Fruit.prototype.constructor)
//function Object() { [native code] }
//可能有人会感到奇怪,在Fruit.prototype中明明并没有该属性,那么这该死的constructor属性从哪里来的。
//我们回到Fruit.prototype属性,点开__proto__属性,那么一切就明朗了。
原型继承补充(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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web API的设计与开发

Web API的设计与开发

[日] 水野贵明 / 盛荣 / 人民邮电出版社 / 2017-6 / 52.00元

本书结合丰富的实例,详细讲解了Web API的设计、开发与运维相关的知识。第1章介绍Web API的概要;第2章详述端点的设计与请求的形式;第3章介绍响应数据的设计;第4章介绍如何充分利用HTTP协议规范;第5章介绍如何开发方便更改设计的Web API;第6章介绍如何开发牢固的Web API。 本书不仅适合在工作中需要设计、开发或修改Web API的技术人员阅读,对想了解技术细节的产品经理、运维人......一起来看看 《Web API的设计与开发》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具