JavaScript中的类式继承和原型式继承

时间:2022-09-01 12:48:03

  最近在看《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继承之原型式继承&lpar;四&rpar;

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

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

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

  6. JavaScript之面向对象学九&lpar;原型式继承和寄生式继承&rpar;

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

  7. 理解JavaScript原型式继承

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

  8. &lbrack;js高手之路&rsqb;原型式继承与寄生式继承

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

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

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

随机推荐

  1. &period;Net HttpPost的发送和接收示例代码

    /// <summary> /// 模拟http 发送post或get请求 /// </summary> /// <param name="Url"& ...

  2. 关于ionic的跨域问题

    例如你的api原地址请求是 http://10.100.100.100:8080/service/, 1.那么你应该在项目内api请求改成 /service/, 注意红色部分是ionic serve ...

  3. 8&period;MVC框架开发(URL路由配置和URL路由传参空值处理)

    1.ASP.NET和MVC的路由请求处理 1)ASP.NET的处理 请求---------响应请求(HttpModule)--------处理请求(HttpHandler)--------把请求的资源 ...

  4. 《C专家编程》读书笔记

    第一章 const float* 表示一个指向float类型常量的指针 第二章 1. 在c语言中const并非真正表示“常量”,在数组定义与case中不可以使用 2. case的一些问题 1: #in ...

  5. A Linear Time Majority Vote Algorithm

    介绍一种算法,它可以在线性时间和常数空间内,在一个数组内找出出现次数超过一半的某个数字. 要解决这个问题并不难,可以使用排序或哈希,但是这两种算法都不能同时满足时间或空间的要求. 然而,该算法(A L ...

  6. HTML5 JavaScript 文件上传

    function fileUpload(targetUrl) { // 隐藏表单名称 var inputName = '_fileselect'; // 文件尺寸 this.fileSize = 0; ...

  7. pb日志查看记录

    因为日志的种类比较多,这里记录下来,方便查看! 1 pb下发日志查看 目前已经确定220-224 603都是这么查看的.手工下发的业务应该都是这么查看的,其实只要去确定步骤2中的序号,就可以直接进入步 ...

  8. 转:spring的启动过程-spring和springMVC父子容器的原理

    要想很好理解这三个上下文的关系,需要先熟悉spring是怎样在web容器中启动起来的.spring的启动过程其实就是其IoC容器的启动过程,对于web程序,IoC容器启动过程即是建立上下文的过程. s ...

  9. bootstrap在ie8下,兼容媒体查询

    最近使用bootstrap做网站的时候发现,在ie8下的媒体查询一直失效: 后来解决了,做如下记录: 1.必须运行在服务器下 2.hack 条件语法,如下: <!--[if lte ie 9]& ...

  10. 深入理解JAVA虚拟机JVM

    深入理解JAVA虚拟机JVM Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制.java之所以能实现一次编写到处执行,也就是因为jVM.原理:编 ...