最近在看《JavaScript设计模式》这本书,虽然内容比较晦涩,但是细品才发现此书内容的强大。刚看完第四章--继承,来做下笔记。

  书中介绍了三种继承方式,类式继承、原型式继承和掺元类继承。类式继承和原型式继承用的比较多,最后一种更像是一种类共享和扩展。本文主要讨论前两者。其实就是讨论如何让一个child对象去继承parent对象的属性和方法。

  1. 类式继承
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中的类式继承和原型式继承的更多相关文章

  1. javascript中类式继承和原型式继承的实现方法和区别

    在所有面向对象的编程中,继承是一个重要的话题.一般说来,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化对象间的耦合(让一个类继承另一个类可能会导致二者产生强耦合).关于“解耦”是程序设计中另 ...

  2. javascript学习笔记--经典继承、组合继承、原型式继承、寄生继承以及寄生组合继承

    经典继承 js中实现经典继承的方式是通过构造函数来实现的,即在子类中对父类调用call方法. function Geometric() { this.time = ""; this ...

  3. js原生继承之——原型式继承实例

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8&qu ...

  4. javascript继承之原型式继承(四)

    javascript之父道格拉斯在2006年给出了这样一串代码,来实现继承. function object(o) { function F() { } F.prototype = o; return ...

  5. JavaScript继承基础讲解,原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承

    说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象J ...

  6. JavaScript之面向对象学九(原型式继承和寄生式继承)

    一.原型式继承 该继承模式是由道格拉斯*克罗克福德在2006年提出的实现继承的方法. 模式的基本思路:借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型. 代码如下: functio ...

  7. 理解JavaScript原型式继承

    0.基础 javascript没有类的概念, javascript不需要实例化某个具体类的实例.javascript对象本身可以用来创建对象, 而对象可以继承自其他对象, 这个概念称为原型式继承 每个 ...

  8. [js高手之路]原型式继承与寄生式继承

    一.原型式继承本质其实就是个浅拷贝,以一个对象为模板复制出新的对象 function object( o ){ var G = function(){}; G.prototype = o; retur ...

  9. Javascript继承4:洁净的继承者----原型式继承

    //原型式继承 function inheritObj(obj){ //声明一个过渡函数对象 function F(){} //过渡对象的原型继承父对象 F.prototype = obj; //返回 ...

随机推荐

  1. Qt自定义事件的实现(军队真正干活,但要增加监军,大平台通知事件,事件内容自定义)

    初学Qt,用了Qt自带的事件,然后想怎么才能定义自己的事件呢?又如何使用自定义事件呢?看了篇文章,说先要子类化QEvent,然后定义自己的QEvent::Type,然后重写QWidget::event ...

  2. Qt 中如何捕获窗口停用和激活的消息

    最近一直在用Qt做一个简单的俄罗斯方块的游戏,由于要实现一个暂停游戏的功能,就是当鼠标移出正在运行的游戏,点击电脑桌面上的其他位置时,这个时候游戏暂停.在这里把实现过程简单的记录一下,作为一个学习笔记 ...

  3. 值得IT运维人员警示的“一件事儿”

    昨天,一个用户打来了紧急求助电话,并且发了邮件,弄得我当时紧张了一下,以为他们那里又出了什么乱子.用户在电话里说:应用系统性能很差,运行很慢,几近“卡死”的感觉,而且重启了多次应用和数据库服务器,最终 ...

  4. Android--广播BroadcastReceiver

    前言 Android四大组件,Activity.Service.ContentProvider.BroadcastReceiver,除了BroadcastReceiver之外,其他的在之前的博客中都有 ...

  5. Largest Number——LeetCode

    Given a list of non negative integers, arrange them such that they form the largest number. For exam ...

  6. MySQL如何利用索引优化ORDER BY排序语句 【转载】

    本文转载自:http://blog.csdn.net/ryb7899/article/details/5580624  .感谢相关作者. MySQL索引通常是被用于提高WHERE条件的数据行匹配或者执 ...

  7. HDU2056(rectangles)

    Rectangles Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  8. 3D效果导航代码

    *, *:before, *:after { padding:; margin:; box-sizing: border-box; } .menu { list-style: none; width: ...

  9. VB编写的验证码生成器

    验证码(CAPTCHA)是“Completely AutomatedPublicTuring test to tell Computers andHumansApart”(全自动区分计算机和人类的图灵 ...

  10. Android 关于获取摄像头帧数据解码

    由于Android下摄像头预览数据只能  ImageFormat.NV21 格式的,所以解码时要经过一翻周折. Camera mCamera = Camera.open(); Camera.Param ...