JavaScript中原型链的那些事
引言
在面向对象的语言中继承是非常重要的概念,许多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承制只继承方法签名,而实现继承继承实际的方法。在ECMAScript中函数没有签名,所以ECMAScript无法实现接口继承,只能实现实现继承。那么是怎么实现实现继承的呢??这就要说一说JS中的原型链了。
原型链的定义
什么是原型链?这个问题很简单,其基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
我们先来回顾一下构造函数,原型,实例之间的关系。每一个构造函数都有一个原型对象,原型对象中包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。在原型对象中通过prototype指向构造函数,而在实例中通过__proto__指向原型对象,但是该属性是区分浏览器的,这是部分浏览器为实例对象添加的属性,在ECMAScript中表现为[[prototype]]。
现在我们已经知道了原型对象中存在一个指针指向构造函数,现在我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,那么另一个原型中也包含一个指向另一个构造函数的指针。加入另一个原型有事另一个类型的实例,那么如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。
我的理解:在我看来,我们可以将原型链理解为一个单链表,每一个原型对象都包含一个指向另一个原型对象的指针,如此递进的链接起来,形式单链表(但并不是说原型链的结构就是单链表,这样只是便于理解)。
function SuperType() {
this.property = true;
}
Super.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue());
//输出:true
可以看出来,在上述代码中,原型链的继承是通过创建SuperType的实例并将实例赋给SubType的原型实现的。本质就是重写原型对象,换成一个新类型的实例。原来存在于SuperType的实例中的所有属性和方法都会存在于SubType.prototype中。最终结果是这样的:instance指向SubType的原型,SubType的原型又指向SuperType的原型。
有一点需要注意的是,instance.constructor现在指向的不是SubType,而是SuperType,原因是SubType的原型指向了另外一个对象SuperType的原型,而这个原型对象的constructor属性指向的是SuperType。
原型搜索机制
通过原型链的实现扩展了原型的原型搜索机制。原型搜索机制就是以读写模式访问一个实例属性时,首先会在实例中搜索该属性,如果没有就会继续搜索实例的原型。通过原型链继承的情况下,搜索过程就会沿着原型链继续向上。首先会搜索实例,在实例中查找是否有需要访问的属性,如果没有,将会搜索实例的原型,看原型中是否有定义的原型属性,如果没有将会通过原型链向上找指向的原型,在找不到属性或方法的情况下回一环一环的到原型链的末端才会停止。
prototype和__proto__的区别
在学习原型链的时候经常搞不懂prototype和__proto__的区别,所以把这两个东西的比较摘出来写成一块。


__proto__属性的来历:创建了自定义的构造函数后其原型对象只会取得constructor属性,其他的方法都是从Object继承的,当使用构造函数的创建一个新的实例的时候该实例内部包含一个指针指向构造函数的原型对象。在ECMAScript中管这个指针叫做[[Prototype]]。在脚本中没有标准的方式访问这个指针。但Firefox、Safari、Chrome在每个对象上都支持一个属性__proto__;但是在其他的实现中,这个属性对脚本是完全不可见的。
默认的原型
所有引用类型默认继承Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,所以在默认原型都会包含一个内部指针,指向Object.prototype。这也是自定义类型都会竭诚toString()、valueOf()等默认方法的原因
谨慎定义方法
1. 给原型添加方法的代码一定要放在替换原型的语句之后。
如下例:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subpeoperty = false;
}
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function() {
return this.subpeoperty;
}
//重写超类型中的方法
SubType.prototype.getSuperValue = function() {
return false;
}
var instance = new SubType();
console.log(instance.getSuperValue());
//输出:false
在上面代码中,重写的方法会屏蔽原来的方法。当通过SubType的实例调用getSuperValue()时,调用的就是重新定义的方法,但通过SuperType的实例调用getSuperValue()时,还会调用原来的方法。
2. 在通过原型链实现继承的时候,不能使用对象字面量创建原型方法。
这样会重写原型链。如下例所示:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype = {
getSubValue:function() {
return this.subproperty;
},
someOtherMethod: function() {
return false;
}
}
var instace = new SubType();
console.log(instace.getSuperValue());
输出:
在上面的例子中,我们把SuperType的实例赋值给原型,紧接着有奖原型替换成一个对象字面量,由于现在的原型包含的是一个Object实例,而非SuperType的实例,一次原型链已经被切断,SuperType和SubType已经没有关系了。
原型链的问题
1. 我们都只知道引用类型的对象中存储的是指向堆内存的指针,所以包含引用类型值的原型属性会被所有实例共享。因为在原型对象中的引用类型只是一个指针,在实例化对象的时候,指针复制,但是指针指向没有发生变化。这也是为什么要在构造函数中,而不是在原型对象中定义属性的原因了。看下面的代码:
function SuperType() {
this.colors = ['red', 'blue', 'green'];
}
function SubType() {
}
SubType.prototype = new SuperType();
var instace1 = new SubType();
instace1.colors.push('black');
console.log(instace1.colors);
var instace2 = new SubType();
console.log(instace2.colors);
//输出:
// ["red", "blue", "green", "black"]
// ["red", "blue", "green", "black"]
需要注意的是,在JS中基本类型值的原型属性并不是这样的:
function SuperType() {
this.property = true;
}
function SubType() {
}
SubType.prototype = new SuperType();
var instace1 = new SubType();
instace1.property = false;
console.log(instace1.property);
var instace2 = new SubType();
console.log(instace2.property);
//输出:
// false
// true
原因相比通过上面的实例大家都知道了,在JS中基本类型值的存储并不是通过指针。
2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。
基于这些问题,在实践中我们会很少单独使用原型链,至于怎么在实践中更好地使用原型链,下一篇博客我会详细讲解。
以上~~
JavaScript中原型链的那些事的更多相关文章
- javascript中原型链与instanceof 原理
instanceof:用来判断实例是否是属于某个对象,这个判断依据是什么呢? 首先,了解一下javascript中的原型继承的基础知识: javascript中的对象都有一个__proto__属性,这 ...
- javascript中原型链存在的问题
我们知道使用原型链实现继承是一个goodway:)看个原型链继承的例子. function A () { this.abc = 44; } A.prototype.getAbc = function ...
- JavaScript中原型链存在的问题解析
我们知道使用原型链实现继承是一个goodway:)看个原型链继承的例子. function A () { this.abc = 44; } A.prototype.getAbc = function ...
- 从问题入手,深入了解JavaScript中原型与原型链
从问题入手,深入了解JavaScript中原型与原型链 前言 开篇之前,我想提出3个问题: 新建一个不添加任何属性的对象为何能调用toString方法? 如何让拥有相同构造函数的不同对象都具备相同的行 ...
- javascript的原型链那些事
如果你对javascript的原型链还有任何疑问,请看这篇文章 进入主题 前言 原型链的规则不百分百适用于所有情况 显式原型:prototype,是一个对象{} 隐式原型:__proto__,是一个对 ...
- JavaScript的原型链继承__propt__、prototype、constructor的理解、以及他们之间相互的关系。
回想自己已经工作了有一段时间了,但是自己对JavaScript的原型链.和继承的理解能力没有到位,最近他们彻底的整理并且复习了一遍. 本案例中部分文案来自网络和书籍,如有侵权请联系我,我只是把我的理解 ...
- Javascript的原型链图
90%的前端或者js程序员或者老师们对Javascript懂得不比这个多 给手机看的 但是这个图里的所有褐色单向箭头链就是Javascript的原型链(颜色标注对理解js原型链很关键) 这图中的各个_ ...
- javaScript系列 [04]-javaScript的原型链
[04]-javaScript的原型链 本文旨在花很少的篇幅讲清楚JavaScript语言中的原型链结构,很多朋友认为JavaScript中的原型链复杂难懂,其实不然,它们就像树上的一串猴子. 1.1 ...
- javascript prototype原型链的原理
javascript prototype原型链的原理 说到prototype,就不得不先说下new的过程. 我们先看看这样一段代码: <script type="text/javasc ...
随机推荐
- 【Django】django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
最近学习Django的过程中,在cmd打算使用python manage.py shell来测试数据的时候,当我一导入自己写的model类,就发现报了这个错误django.core.exception ...
- poj 1639 最小k度限制生成树
题目链接:https://vjudge.net/problem 题意: 给各位看一下题意,算法详解看下面大佬博客吧,写的很好. 参考博客:最小k度限制生成树 - chty - 博客园 https:/ ...
- Java中CountDownLatch和CyclicBarrier
Java编程思想中的例子import javax.validation.constraints.Size; import java.util.Random; import java.util.conc ...
- 【python中二叉树的实现】python中二叉树的创建、三种方式递归遍历和非递归遍历
代码如下: # coding=utf-8 class myNode(object): def __init__(self, data=-1, lchild=None, rchild=None): se ...
- 10.13 新版本go on~
上午1.5 终审 and 排期 合同管理那边又是切换选项时各种联动,我第一想法是 好麻烦,不想做这个...第二想法才是给我做吧 锻炼锻炼我 然后 分任务的时候 分给我了,,哈哈 开心 虽然我没想躲 但 ...
- CentOS7 开启网卡,设置开机启用网卡
默认centos和redhat7都是不启用有线网卡的,要么手动开启,要么安装时直接启用(安装时启用网卡和指定IP最省事)! 一 .临时启用网卡,关机或重启后,网络不会自动开启1.使用命令 ip add ...
- redis在游戏服务器中的使用初探(四) redis应用
文章系列先介绍环境搭建 介绍redis操作和代码编写运行 这是典型的实战工程过程.那么我们为何要使用redis而不是常规的数据库比如 mysql呢? 因为KV内存数据库最大的优势所有数据全部存储在内 ...
- 在cmd下可以import cv2,而Pycharm报错:找不到cv2
平台:win10 x64+Pycharm+Anaconda3+opencv 安装教程:参考博客——http://blog.sina.com.cn/s/blog_cca23c300102xiy4.htm ...
- javascript(作业九)
1.javascript简介: JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端 ...
- Paper | Contrast Limited Adaptive Histogram Equalization
目录 1. 背景 1.1. 对比度和直方图均衡HE 1.2. HE的问题 1.3. AHE 1.4. 底噪问题 2. CLAHE 2.1. 效果展示 2.2. 算法格式和细节 论文:Contrast ...