图解JavaScript 继承
JavaScript作为一个面向对象语言,可以实现继承是必不可少的,但是由于本身并没有类的概念(不知道这样说是否严谨,但在js中一切都类皆是对象模拟)所以在JavaScript中的继承也区别于其他的面向对象语言。可能很多初学者知道实现js继承的方法,但却对实现继承的原理一头雾水。所以,今天我们就来图解JavaScript继承。(因为继承有关于原型相关知识,所以希望大家对原型有一定的了解推荐阅读:理解JavaScript原型 梳理JavaScript原型整体思路)。
下面我们就开始看看JavaScript各种继承方式及他们的原理
1.默认模式
/**
* [默认继承模式]
*/
function Parent(name) {
this.name = name || 'Adam';
}
var parent = new Parent();
Parent.prototype.say = function() {
return this.name;
};
function Child(name) {}
Child.prototype = new Parent();
var kid = new Child();
console.log(kid.hasOwnProperty('name'));//false
console.log(parent.hasOwnProperty('name'));//true
console.log(kid.say());//Adam
kid.name = 'lili';
console.log(kid.say());//lili
console.log(parent.say());//Adam上面代码实现继承的是第12行的 Child.prototype = new Parent();通过这句代码将Child的原型成为Parent的一个实例。

根据上图可以看出,Parent是一个构造函数,并且有一个Parent.prototype对象,对象内部有一个say()方法(此处不一一列举prototype其他那只属性方法)。又存在一个Child构造函数,我们让Parent实例化出的对象当作Cihld的prototype(毕竟prototype也是一个对象,所以无何不可)。其实为了更方便理解,这句话可以改成 var parent1 = new Parent(); Child.prototype = parent1l; 这样就比较显而易见了。这样赋值的结果就是:Child是Parent实例化出的对象,所以Child.__proto__是Parent.prototype。kid为Cihld实例化出的对象,所以:kid.__proto__是Parent 建立原型链 kid--->Child--->Parent(Child.prototype)--->Parent.prototype 由此形成继承。
默认模式的方式没有继承上级构造函数自身的属性,只是可以通过原型链向上查找而使用它而已。如果继承者为自己设置该属性,则会屏蔽原型链上的其他同名属性。
看一看上面代码的输出可以看出14、15行证明继承而来的属性并没能在自身创造一个新的该属性,只是通过原型向上查找的方式来获取该属性,正因为如此16~19行的输出可以看出,kid对name属性的更改会影响到父构造函数中的name属性。
2.借用构造函数
/**
* [借用构造函数]
*/
function Article(tags) {
this.tags = tags || ['js', 'css'];
}
Article.prototype.say = function() {
return this.tags
}
var article = new Article();
function StaticPage(tags) {
Article.apply(this,arguments);
}
var page = new StaticPage();
console.log(page.hasOwnProperty('tags'));//true
console.log(article.hasOwnProperty('tags'));//true
console.log(page.tags);//['js', 'css']
page.tags = ['html', 'node'];
console.log(page.tags);//['html', 'node']
console.log(article.tags);//['js', 'css']
//console.log(page.say());//报错 undefined is not a function
console.log(article.say());//['js', 'css']
上面代码实现继承的是第12行的Article.apply(this,arguments);通过这句代码通过使用apply方法调用Article构造函数更改this指向(关于this:JavaScript中我很想说说的this)。

从上图可以很明显看出Article与StaticPage并没有连接,也就是说使用借用构造函数的方式,因为直接以修改调用位置的方法使用Article构造函数,所以继承了Article内部的属性,独立创建出属性,但是由于没有使用StaticPage.prototype所以StaticPage会自动创建出一个空的prototype对象。所以StaticPage并没有继承到Article原型链上的方法。
在上面的例子代码中有很多个输出,现在我们来研究一下输出那些答案的原因并借以证明上面的话~
首先15、16行判断tags是不是article和page(注意这是两个实例化出的对象)的自身属性,返回值皆为true,由此可以说明StaticPage的确继承了Article中添加到this的属性。
17、18、19、20行中,在page没有为tags专门赋值时可输出父构造内部tags的值即 ['js', 'css'] 当赋值为 ['html', 'node'] 后page的tags值改变但article的值并没有改变(20行),由此可见StaticPage继承Article是独立创造了其内部的属性(因为是修改调用位置的方式,所以会创建新的属性而不会产生关联)。
21、22行调用say方法。报错证明page并没能继承到Article.prototype上的方法。
3.借用和设置原型(组合继承)
/**
* 借用和设置原型
*/
function Bird(name) {
this.name = name || 'Adam';
}
Bird.prototype.say = function() {
return this.name;
};
function CatWings(name) {
Bird.apply(this, arguments);
}
CatWings.prototype = new Bird();
var bird = new CatWings("Patrick");
console.log(bird.name);//Patrick
console.log(bird.say());//Patrick
delete bird.name;
console.log(bird.say());//Adam
借用和设置原型的方式是最常用的继承模式,它是结合前面两种模式,先借用构造函数再设置子构造函数的原型使其指向一个构造函数创建的新实例。

首先CatWings使用借用构造函数的方式创建新的示例bird这样bird可以独立创建出name属性而不用与父构造函数的name有关联。再将CatWings.prototype赋给Bird的实例化对象,这样又将这两个构造函数连接在一起是bird对象可以访问父构造函数原型链上的方法。
4.共享原型
/**
* 共享原型
*/
function A(name) {
this.name = name || 'Adam';
}
A.prototype.say = function(){
return this.name;
};
function B() {}
B.prototype = A.prototype;
var b = new B();
console.log(b.name);
b.name = 'lili';
console.log(b.say());
上面代码实现继承的是第11行的B.prototype = A.prototype;通过这句代码将B的原型更改为A的原型。

这种方法很简单,没有什么太多需要解释的,但是它的弊端也很大:它并不能继承到父构造内部的属性,而且也只是可以使用父构造原型上的属性方法,并且子对象更改原型链上的属性或方法同时会影响到父元素~
5.临时构造函数
/**
* 临时构造函数
*/
function C(name) {
this.name = name || 'Adam';
}
C.prototype.say = function() {
return this.name;
};
function D() {}
var E = function() {};
E.prototype = C.prototype;
D.prototype = new E();
临时构造函数的意思就是通过一个临时的构造函数来实现继承,正如上面代码的11、12行。

这个图可能画的不是那么易于理解,但我们的重点放在D.prototype那个椭圆上,你就会发现上面同样写着 new E() 是的他就是一个E构造函数的实例,而E在整个继承过程中不会出现实际的用处,他的作用只是为了对父构造和子构造做一个连接,所以被称为临时构造函数。这样做的优点是什么呢? 首先他能解决共享原型的最大弊端就是可以同时更改同一个原型并且会影响到其他人,但这种方法中,虽然E与C是共享原型,但D使用过默认继承的方式继承的原型,就没有权限对C.prototype进行更改。
6.原型继承
原型继承与上述几种继承模式有着很大的区别,上面的继承模式皆是模拟类的继承模式,但原型继承中并没有类,所以是一种无类继承模式。
/**
* [原型继承]
* @type {Object}
*/
function object(proto) {
function F() {}
F.prototype = proto;
return new F();
} var person = {
name: 'nana',
friends: ['xiaoli', 'xiaoming']
}; var anotherPerson = object(person);
anotherPerson.friends.push('xiaohong');
var yetAnotherPerson = object(person);
anotherPerson.friends.push('xiaogang');
console.log(person.friends);//["xiaoli", "xiaoming", "xiaohong", "xiaogang"]
21 console.log(anotherPerson.__proto__)//Object {name: "nana", friends: Array[4]}
可以看到上面5~9行object函数通过object函数我们实现了原型继承。而在整个代码中,虽然实现了anotherPerson和yetAnotherPerson对person这个对象的继承,但其中并没有构造函数。

由上图可以看出,因为构造函数的原型本就是一个对象,现在将一个需要被继承的对象设定为一个构造函数F的原型,并用这个构造函数实例化出一个对象anotherPerson,这样,这个对象anotherPerson就可以通过原型链查找找到person这个对象,并使用他上面的属性或者方法。
在ECMAScript5中,这种模式已经通过方法Object.create()来实现,也就是说,不需要推出与object()相类似的函数,他已经嵌在JavaScript语言之中。
由于在我对JavaScript继承的学习过程中有了很多对实现原理不理解的地方,导致我一直不能记住并且正确使用这部分知识,所以当我感觉自己对继承有了一部分了解之后写下了这篇博客,博客中的内容都是我个人的理解,如果有解释不到位或理解有偏差的地方还请大神告知,小女子在此谢过~
图解JavaScript 继承的更多相关文章
- 图解Javascript原型链
本文尝试阐述Js中原型(prototype).原型链(prototype chain)等概念及其作用机制.上一篇文章(图解Javascript上下文与作用域)介绍了Js中变量作用域的相关概念,实际上关 ...
- javascript继承的三种模式
javascript继承一般有三种模式:组合继承,原型式继承和寄生式继承: 1组合继承:javascript最为广泛的继承方式通过原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承,同 ...
- javascript继承机制的设计思想(ryf)
我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例" ...
- 【读书笔记】javascript 继承
在JavaScript中继承不像C#那么直接,C#中子类继承父类之后马上获得了父类的属性和方法,但JavaScript需要分步进行. 让Brid 继承 Animal,并扩展自己fly的方法. func ...
- JavaScript强化教程——Cocos2d-JS中JavaScript继承
javaScript语言本身没有提供类,没有其它语言的类继承机制,它的继承是通过对象的原型实现的,但这不能满足Cocos2d-JS引擎的要求.由于Cocos2d-JS引擎是从Cocos2d-x演变而来 ...
- [原创]JavaScript继承详解
原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...
- javascript继承(六)—实现多继承
在上一篇javascript继承—prototype最优两种继承(空函数和循环拷贝)(3) ,介绍了js较完美继承的两种实现方案,那么下面来探讨一下js里是否有多继承,如何实现多继承.在这里可以看看j ...
- javascript继承(五)—prototype最优两种继承(空函数和循环拷贝)
一.利用空函数实现继承 参考了文章javascript继承—prototype属性介绍(2) 中叶小钗的评论,对这篇文章中的方案二利用一个空函数进行修改,可以解决创建子类对象时,父类实例化的过程中特权 ...
- javascript继承(四)—prototype属性介绍
js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向 ...
随机推荐
- hbase批量数据导入报错:NotServingRegionException
批量导入数据到hbase的时候,报错: org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException: Failed 1 ac ...
- Git: 生成ssh公钥
生成 SSH 公钥 大多数 Git 服务器都会选择使用 SSH 公钥来进行授权.系统中的每个用户都必须提供一个公钥用于授权,没有的话就要生成一个.生成公钥的过程在所有操作系统上都差不多. 首先先确认一 ...
- dbm速算
经常用到,但是经常搞忘记.在这里记录一下换算的一些技巧. 为什么要用dB 在最前面,需要解释一下dB的由来,这样会让理解变得简单一点.事实上,dB(分贝)是一个纯计数单位.使用dB的目的呢,其实就是用 ...
- java字符串拼接与性能
使用 Concatenation Operator (+) String concat method – concat(String str) StringBuffer append method – ...
- 【Alpha阶段】第五次Scrum例会
由于软工整个项目规划延期1周,我们将停止2天的Scrum,进行相应的修整 会议信息 时间:2016.10.21 22:30 时长:20min 地点:大运村1号公寓5楼楼道 类型:日常Scrum会议 个 ...
- Mysql学习笔记(二)对表结构的增删改查
有将近一个星期都没有更新mysql了.相反linux的东西倒是学习不少.可能我个人情感上对linux更感兴趣一点.但mysql我也不烦,只是一旦将精力投入到了一样事情上去,就很难将精力分散去搞其他的东 ...
- DNS(一)之禁用权威域名服务器递归解析
DNS dns是互联网中最核心的带层级的分布式系统,负责把域名解析成ip,把IP解析出域名,以及宣告邮件路由信息等等,使得使用域名访问网站,收发邮件成了可能. bind(berkeley Intern ...
- LightOj1089(求点包含几个线段 + 线段树)
题目链接 题意:n( n <= 50000 ) 个线段,q ( q <= 50000) 个点,问每个点在几个线段上 线段端点的和询问的点的值都很大,所以必须离散化 第一种解法:先把所有的线 ...
- python不同模式打开文件的完全列表
模式 描述 r 以只读方式打开文件.文件的指针将会放在文件的开头.这是默认模式. rb 以二进制格式打开一个文件用于只读.文件指针将会放在文件的开头.这是默认模式. r+ 打开一个文件用于读写.文件指 ...
- window.location.href的用法
在写ASP.Net程序的时候,我们经常遇到跳转页面的问题,我们经常使用Response.Redirect 做ASP.NET框架页跳转,如果客户要在跳转的时候使用提示,这个就不灵光了,如: Respon ...