JavaScript中的类式继承和原型式继承
最近在看《JavaScript设计模式》这本书,虽然内容比较晦涩,但是细品才发现此书内容的强大。刚看完第四章--继承,来做下笔记。
书中介绍了三种继承方式,类式继承、原型式继承和掺元类继承。类式继承和原型式继承用的比较多,最后一种更像是一种类共享和扩展。本文主要讨论前两者。其实就是讨论如何让一个child对象去继承parent对象的属性和方法。
- 类式继承
function Parent(name){
this.name = name;
this.sex = "male";
} Parent.prototype.getName = function(){
alert(this.name);
} Parent.prototype.show = function(){
alert("I'm here.");
} function Child(age){
this.age = age;
}
在这里我们看到parent类有name和sex属性,并且有getName方法和show方法,而child类只有age属性。parent和child都是构造函数。
让child具有parent的属性,我们可以用apply或者call来把parent的构造函数绑定到child上,如下:
function Child(age){
this.age = age;
Parent.call(this,"Jim")
} var c = new Child(12);
console.log(c.name) //Jim
console.log(c.sex) //male
但是这种方法只是继承了parent的构造函数,并没有得到parent.prototype上定义的方法,因此c.getName()和c.show()会提示c没有该方法。
当然上面提到的方法并不是类式继承,接下来我们看看怎样用prototype模式来实现类式继承。
function Child(age){
this.age = age;
Parent.call(this,"Jim");
} Child.prototype = new Parent();
Child.prototype.constructor = Child; var c = new Child(12);
通过prototype来继承,能继承父类所有的属性和方法。当然如果没有
Parent.call(this,"Jim")
这句话,虽然c继承了parent的name属性和getName方法,但是返回结果是undefined哦,不过确实是继承到了。
我们把child.prototype作为parent的一个实例,也就是改写了child的prototype对象,任何一个prototype对象都有一个constructor属性,指向它的构造函数。也就是说,child.prototype 这个对象的constructor属性,是指向child的。在这里我们改写了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,需要我们手动加上去,否则后面的”继承链”会出问题。
还有一种方法就是创建一个空对象作为中介来实现,不过这个方法有个问题,请看
function Child(age){
this.age = age;
}
function F(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
var c = new Child(12);
这里的F()就是我们创建的用来作为中介的空对象,我们来测试下
console.log(c.sex) //undefined
c.show() // "I'm here."
问题就在这里,这种方法只继承到了父类原型上的属性和方法,而没有继承父类的构造函数,更别说拥有父类的属性,比如sex。
不过解决方法很简单,只要用到我们前面提到的call或者apply把parent的构造函数绑定到child上就可以了,因此只要在child()构造函数里加上
Parent.call(this,"Jim")
为了简化,我们可以结合apply或者call和空对象的方法,把他们封装为一个extend函数
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
}
我们只要这样写 extend(Child,Parent),并且在Child()构造函数中绑定Parent的构造函数即可。不过麻烦的是如果child不想继承parent了,想要继承uncle类了,我们又得修改child构造函数中的代码,把
Parent.call(this,"Jim")
修改为
Uncle.call(this,"Jim")
为解决这个问题,书中把extend函数就行了改进
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass; subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) { //确保constructor属性正确设置
superClass.prototype.constructor = superClass;
}
}
然后在子类child中这样写
Child.superclass.constructor.call(this, “Jim”);
当然那个讨厌的Jim你可以换成name,并通过Child构造函数的实参传入。或者直接换成apply(this,arguments),你只要传入参数即可。
好了,类式继承大概就是这个意思了。其实这个名字我觉得奇怪,而且经常把这些个方法误认为原型式继承,毕竟处处离不开prototype。大概是这种方法比较类似于其他面向对象编程语言的类继承吧。
2. 原型式继承
那么原型式继承是怎样的呢?我们先来总结下类式继承的要点:1.首先用一个类的声明定义对象的结构,其实就是构造函数啦;2.接下来实例化改类创建一个新的对象。用这种方式创建的对象都有一套该类的所有实例属性的副本,每一个实例方法都只存在一份,但每个对象都有一个指向它的连接。试想如果该类有很多实例,每个实例都有个各自的属性副本,那得多占用内存啊。
使用原型式继承的时候我们不需要创建构造函数来定义对象的结构,我们直接创建一个对象,并把需要让子对象继承的属性和方法都写到这个对象里面,这个对象称为原型对象,然后通过一个clone函数让我们的子对象继承这个原型对象。
function clone(object) {
function F() {}
F.prototype = object;
return new F;
} var Parent = {
name: "Jim",
sex: "male",
getName : function(){
return this.name;
}
} var c = clone(Parent);
书中用clone这个名字我觉得不大合适,毕竟这并没有对原型对象进行拷贝,只是将prototype指向了原型对象,从而通过原型链获得了原型对象的属性和方法。打个比方,一个A对象没有什么权利,但是她找到了干爹,也就是原型对象。这个找干爹的过程就是clone函数中的F.prototype = object。这样A有了靠山,下次自己本身没有能力办到的事情,就可以找她干爹也就是原型对象办了。当然这个比喻有点不搭恰当。总之通过将一个对象的prototype指向某个原型对象,使一个对象拥有了原型对象的属性和方法。因为自身没有的属性和方法,都可以通过原型链去原型对象上找,当然继承之后该对象也可以定义自己的属性和方法,同时也可以对继承来的属性和方法进行改写,成为自己独有的属性和方法。
console.log(c.name); //Jim
c.name = "Tom";
c.getName(); //Tom
可以发现原型式继承比类式继承要更为简洁,不过该方法有一个问题,那就是读和写的不对等性。var c = clone(Parent)实际上只是创建了一个空对象{},只不过它的prototype指向原型对象而已。因此在我们第一次console.log(c.name)的时候,只是通过原型链去去了原型对象的name属性,而且之后c.name = "Tom"这句话并不是改写从原型对象继承过来的name属性,而是创建了一个自身的新的name属性,我们运行一下console.log(c)看看,得到如下信息
F {age: 12, name: "Tom", name: "Jim", sex: "male", getName: function}
name: "Tom"
__proto__: Object
getName: function (){}
name: "Jim"
sex: "male"
看到有两个name属性,一个是c自身的,一个是原型对象上的。此时c.name是Tom,因为自己已有该属性,不必去原型对象上找。那么此时的c.getName()是多少呢,是Tom哦,因为c拥有了getName方法,而该方法中的this是指向调用该方法的对象的,这里是c,所以是Tom。
所有这里有一个更坑爹问题就是,当原型对象包含数组和对象类型成员的时候,直接看代码吧。
var Parent = {
name: "Jim",
sex: "male",
hobby: ["sports"],
children:{son:"Jack",daughter:"Lily"},
getName : function(){
return this.name;
}
} var c = clone(Parent);
c.hobby.push("game");
c.children.son = "James"; console.log(Parent.hobby); //["sports","game"]
console.log(Parent.children.son); //"James"
看到了吧,Parent被篡改了。不难理解,因为刚创建的c本身没有hobby和children,只能去改原型对象了。所以要想只修改c上的hobby和children,必须得先创建副本。
var c = clone(Parent);
c.hobby = [];
c.children = {son:"Jack",daughter:"Lily"};
c.hobby.push("game");
c.children.son = "James"; console.log(Parent.hobby); //["sports"]
console.log(Parent.children.son); //"Jack"
console.log(c.hobby); //["game"]
console.log(c.children.son); //"James"
不过这样的话就要在继承数组和成员对象的的时候就要知道数组和成员对象的结构和原始值,的确比较麻烦。书中也给出了解决方法,大概思路就是通过原型对象中的方法来创建数组或是对象,这样c只要调用原型对象的方法就可以继承数组或是成员对象。
3. 类式继承和原型式继承的比较
两种方法都可以很好的达到继承的效果。相比之下,类式继承可能更适合面向对象的编程习惯,而原型式继承是javascript语言特有的,理解起来比较奇怪,尤其是前面提到的读写性不对等,子类的修改甚至会影响到父类和其他继承该父类的子类等。不过原型式继承更为简洁,同时在内存使用方面更为节约,因为都是通过原型对象共享属性和方法的么,不像类式继承中每个实例都有一个副本,比较占用内存。总之两者继承方式各有利弊,怎么取舍看情况而定。
好了,在博客园的第一篇博文总算码完了,水平有限,如有纰漏还希望大家之中交流。
参考资料:
1.重温Javascript继承机制 http://www.w3cfuns.com/forum.php?mod=viewthread&tid=1086&fromuid=5402010
2.JavaScript设计模式(Ross Harmes / Dustin Diaz) 第四章--继承
JavaScript中的类式继承和原型式继承的更多相关文章
- javascript中类式继承和原型式继承的实现方法和区别
在所有面向对象的编程中,继承是一个重要的话题.一般说来,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化对象间的耦合(让一个类继承另一个类可能会导致二者产生强耦合).关于“解耦”是程序设计中另 ...
- javascript学习笔记--经典继承、组合继承、原型式继承、寄生继承以及寄生组合继承
经典继承 js中实现经典继承的方式是通过构造函数来实现的,即在子类中对父类调用call方法. function Geometric() { this.time = ""; this ...
- js原生继承之——原型式继承实例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8&qu ...
- javascript继承之原型式继承(四)
javascript之父道格拉斯在2006年给出了这样一串代码,来实现继承. function object(o) { function F() { } F.prototype = o; return ...
- JavaScript继承基础讲解,原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承
说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象J ...
- JavaScript之面向对象学九(原型式继承和寄生式继承)
一.原型式继承 该继承模式是由道格拉斯*克罗克福德在2006年提出的实现继承的方法. 模式的基本思路:借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型. 代码如下: functio ...
- 理解JavaScript原型式继承
0.基础 javascript没有类的概念, javascript不需要实例化某个具体类的实例.javascript对象本身可以用来创建对象, 而对象可以继承自其他对象, 这个概念称为原型式继承 每个 ...
- [js高手之路]原型式继承与寄生式继承
一.原型式继承本质其实就是个浅拷贝,以一个对象为模板复制出新的对象 function object( o ){ var G = function(){}; G.prototype = o; retur ...
- Javascript继承4:洁净的继承者----原型式继承
//原型式继承 function inheritObj(obj){ //声明一个过渡函数对象 function F(){} //过渡对象的原型继承父对象 F.prototype = obj; //返回 ...
随机推荐
- NodeJs安装与使用入门
一.NodeJs简介 NodeJS官网上的介绍: Node.js is a platform built on Chrome's JavaScript runtime for easily bui ...
- Hbase 设计与开发实战
Hbase 概述 大数据及 NoSQL 的前世今生 传统的关系型数据库处理方式是基于全面的 ACID 保证,遵循 SQL92 的标准表设计模式(范式)和数据类型,基于 SQL 语言的 DML 数据交互 ...
- codeforces 341C Iahub and Permutations(组合数dp)
C. Iahub and Permutations time limit per test 1 second memory limit per test 256 megabytes input sta ...
- html5 教程
http://www.tutorialspoint.com/html5/index.htm
- 在JAVA中 System.getProperty 和 System.setProperty 方法.
今天着手研究TOMCAT源码. 在刚開始的时候Startup类中init方法中调用非常多次System.getProperty和System.setProperty的方法. 后来经过网上搜索才得知,这 ...
- Brunch:快捷的HTML5构建工具
Brunch,一个超快的HTML5构建工具.它可以(官方介绍): 编译你的脚本,模板,样式,链接它们, 将脚本和模板封装进common.js/AMD模块里,链接脚本和样式, 为链接文件生成源地图,复制 ...
- 设计模式--迪米特法则(Lod/LKP)
迪米特法则:(Law of Demeter, LoD),也称最少知识原则(Least Knowledge Principle, LKP) 理解: 假设两个类不必彼此直接通信,那么这两个类就不 ...
- Oracle按用户进行统计信息更新
按用户进行统计信息更新 PL/sqldev工具使用system用户连接到oracle,打开命令窗口执行以下SQL,用户名请根据实际情况进行更改: begin dbms_stats.gather_sch ...
- centos5.5上apache快速安装H264流媒体支持MP4-H264边下边播
2013年的某一天,客户反馈北京同事做的广告视频下载速度好慢,几MB的视频在手机上要下载接近一分钟才能开始播放. 我分析后发现两点:1)托管的服务器没支持流媒体:2)广告视频MP4并非流媒体格式. 对 ...
- mui实现自动登录
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta name= ...