javascript虽然是一门面向对象的语言,可是它的继承机制从一开始设计的时候就不一样于传统的其余面向对象语言,是基于原型的继承机制,可是在这种机制下,继承依然有一些不一样的实现方式。javascript
方法一:类式继承java
所谓的类式继承就是指模仿传统面向对象语言的继承方式,继承与被继承的双方都是“类”,代码以下:程序员
首先定义一个父类(或超类):设计模式
1 function Person(name){ 2 this.name=name; 3 } 4 5 Person.prototype.getName=function(){ 6 return this.name; 7 };
该父类person的属性在构造函数中定义,能够保证继承它的子类的name属性不与它共享这个属性,而是单独属于子类,将getName方法挂载到原型上,是为了让继承它的子类的多个实例共享这一个方法体,这样可以节省内存,(对于多个实例来说,每次New一个实例出来都会保证这些实例的getName方法引用的是同一段内存空间,而不是独立的空间)。函数
定义一个继承方法extend,以下:this
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){ superClass.prototype.constructor=superClass; } }
在这个方法中,首先建立一个新的类为F,让它的原型为父类的原型,而且让子类的原型指向该类F的一个实例,从而达到了一个继承父类的目的,同时,子类的原型因为被修改,因此将修改后的原型的constructor属性指向子类,让其拥有构造函数,同时给该子类挂载一个superClass属性,子类能够经过该属性调用父类,从而创建起了子类和父类的关系。spa
定义一个子类Author来继承父类Person,以下:prototype
1 function Author(name,books){ 2 Author.superClass.constructor.call(this,name); 3 this.book=books; 4 } 5 extend(Author,Person); 6 Author.prototype.getBooks=function(){ 7 return this.book; 8 }
这里在子类的构造函数中经过其superClass属性调用父类的构造函数,同时采用call方法,转换该方法调用的this指向,让子类Author也拥有(继承)父类的属性,同时子类又拥有本身的属性book,因此在构造函数中将参数books赋值给属性book,达到构造的目的。采用extend函数继承父类Person原型上的属性和方法(实际上只继承了方法,由于咱们以前只是将方法挂载到了原型上,属性是在构造函数中定义的)。同时,Author又拥有本身的方法getBooks,将其挂载到对应的原型上,达到了在继承的基础上进一步扩充自身的目的。设计
这种继承方式很明显就是采用相似于传统面向对象语言的类式继承,优势是对习惯于传统面向对象概念的程序员来说很容易理解,缺点是过程比较繁琐,内存消耗稍大,由于子类也拥有本身的构造函数及原型,并且子类和父类的属性彻底是隔离的,即便二者是同样的值,可是不能共享同一段内存。code
方法二:原型式继承
首先定义一个父类,这里不会刻意模仿使用构造函数来定义,而是直接采用对象字面量的方式定义一个对象,该对象就是父类
1 var Person={ 2 name:'default name', 3 getName:function(){ 4 return this.name; 5 } 6 7 } ;
和第一种方法同样,该对象拥有一个属性name和一个方法getName .
而后再定义一个克隆方法,用来实现子类对父类的继承,以下:
1 function clone(obj){ 2 function F(){} 3 F.prototype=obj; 4 return new F(); 5 }
该克隆方法新建一个对象,将该对象的原型指向父类即参数obj,同时返回这个对象。
最后子类再经过克隆函数继承父类,以下:
1 var Author=clone(Person); 2 Author.book=['javascript']; 3 Author.showBook=function(){ 4 return this.book; 5 }
这里定义一个子类,经过clone函数继承父类Person,同时拓展了一个属性book,和一个方法showBook,这里该子类也拥有属性name,可是它和父类的name值是同样的,因此没有进行覆盖,若是不同,能够采用
Author.name='new name';覆盖这个属性,从而获得子类的一个新的name属性值。
这种原型式继承相比于类式继承更为简单天然,同时若是子类的属性和父类属性值相同,能够不进行修改的话,那么它们二者其实共享的是同一段内存空间,如上面的name属性,缺点是对于习惯了传统面向对象的程序员难以理解,若是二者要进行选择的话,无疑是这种方式更为优秀一些。
既然javascript中采用基于原型的方式来实现继承,并且每一个对象的原型只能指向某个特定的类的实例(不能指向多个实例),那么如何实现多重继承(即让一个类同时具备多个类的方法和属性,并且自己内部不本身定义这些方法和属性)?
在javascript设计模式中给出了一种掺元类(mixin class)的方式:
首先定义一个掺元类,用来保存一些经常使用的方法和属性,这些方法和属性能够经过拓展的方式添加到任何其余类上,从而被添加类就具备了该类的某些方法和属性,若是定义多个掺元类,同时添加给一个类,那么该类就是间接实现了“多重继承”,基于这种思想,实现以下:
掺元类定义:
1 var Mixin=function(){}; 2 Mixin.prototype={ 3 serialize:function(){ 4 var output=[]; 5 for(key in this){ 6 output.push(key+":"+this[key]); 7 } 8 return output.join(','); 9 } 10 }
该掺元类具备一个serialize方法,用来遍历其自身,输出自身的属性和属性值,而且将他们以字符串形式返回,中间用逗号隔开。
定义一个扩充方法,用来使某个类通过扩充以后具有掺元类的属性或方法,以下:
1 function augment(receivingClass,givingClass){ 2 if(arguments[2]){ 3 for(var i= 2,len=arguments.length;i<len;i++){ 4 receivingClass.prototype[arguments[i]]=givingClass.prototype[arguments[i]]; 5 } 6 } 7 else 8 { 9 for(methodName in givingClass.prototype){ 10 if(!receivingClass.prototype[methodName]){ 11 receivingClass.prototype[methodName]=givingClass.prototype[methodName]; 12 } 13 } 14 15 } 16 }
该方法默认是两个参数,第一个参数是接受被扩充的类,第二个参数是掺元类(用来扩充其余类的类),还能够有其余参数,若是大于两个参数,后面的参数都是方法或者属性名,用来表示被扩充类只想继承掺元类的指定的属性或方法,不然默认继承掺元类的全部属性和方法,在该函数中,第一个if分支是用来继承指定属性和方法的情形,else分支是默认继承全部属性和方法的情形。该方法的实质是将掺元类的原型上的属性和方法都扩充(添加)到了被扩充类的原型上面,从而使其具备掺元类的属性和方法。
最后,使用扩充方法实现多重继承
1 augment(Author,Mixin); 2 var author= new Author('js',['javascript design patterns']); 3 alert(author.serialize());
这里定义了一个author的类,该类继承自Person父类,同时又具有掺元类Mixin的方法和属性,若是你愿意,能够定义N个掺元类用来扩充该类,它一样可以继承你定义的其余掺元类的属性和方法,这样就实现了多重继承,最后,author的serialize方法的运行结果以下:
你会发现该类同时具备person类,Author类,Mixin类的属性和方法,其中Person和Mixin的属性和方法都是经过“继承”得来的,从实际上来说,它实现了多重继承。