淺析JavaScript繼承方式
前段時(shí)間溫故了下JavaScript 的寫(xiě)類(lèi)方式,從這篇開(kāi)始我們看看JavaScript 的繼承方式。
面向?qū)ο蟮恼Z(yǔ)言多數(shù)都支持繼承,繼承最重要的優(yōu)點(diǎn)就是代碼復(fù)用,從而構(gòu)建大型軟件系統(tǒng)。如果一個(gè)類(lèi)能夠重用另一個(gè)類(lèi)的屬性和或方法,就稱(chēng)之為繼承。從這個(gè)角度來(lái)看看JS的繼承方式。JS中繼承方式與寫(xiě)類(lèi)方式息息相關(guān)。不同的寫(xiě)類(lèi)方式造成不同的繼承方式。各種流行JavaScript庫(kù)繼承方式也各不相同。從最簡(jiǎn)單的復(fù)用開(kāi)始。
1、構(gòu)造函數(shù)方式寫(xiě)類(lèi),通過(guò)方法調(diào)用復(fù)制父類(lèi)屬性/字段到子類(lèi) 實(shí)現(xiàn)繼承
這里父類(lèi),子類(lèi)都采用構(gòu)造函數(shù)方式寫(xiě),不用原型。子類(lèi)調(diào)用父類(lèi)函數(shù)來(lái)復(fù)制父類(lèi)的屬性。
- /**
 - * 父類(lèi)Polygon:多邊形
 - * @param {Object} sides
 - */
 - function Polygon(sides) {
 - this.sides = sides;
 - this.setSides = function(s) {this.sides=s;}
 - }
 - /**
 - * 子類(lèi)Triangle:三角形
 - */
 - function Triangle() {
 - this.tempfun = Polygon;//父類(lèi)引用賦值給子類(lèi)的一個(gè)屬性tempfun
 - this.tempfun(3);//調(diào)用
 - delete this.tempfun;//刪除該屬性
 - this.getArea = function(){};
 - }
 - //new個(gè)對(duì)象
 - var tri = new Triangle();
 - console.log(tri.sides);//繼承的屬性
 - console.log(tri.setSides);//繼承的方法
 - console.log(tri.getArea);//自有的方法
 - //缺點(diǎn)是對(duì)于Triangle的實(shí)例對(duì)象用instanceof為父類(lèi)Polygon時(shí)是false
 - console.log(tri instanceof Triangle);//true
 - console.log(tri instanceof Polygon);//false
 
因?yàn)?JavaScript中具名函數(shù)的多種調(diào)用方式 ,子類(lèi)還可以有以下的多種實(shí)現(xiàn)方式。只是在子類(lèi)中調(diào)用父類(lèi)方法不同而已。
- function Triangle() {
 - Polygon.call(this,3); //call方式調(diào)用父類(lèi)
 - this.getArea = function(){};
 - }
 - function Triangle() {
 - Polygon.apply(this,[3]); //apply方式調(diào)用父類(lèi)
 - this.getArea = function(){};
 - }
 - function Triangle() {
 - var temp = new Polygon(3); //new方式調(diào)用父類(lèi)
 - for(atr in temp) { //全部復(fù)制給子類(lèi)
 - this[atr] = temp[atr];
 - }
 - this.getArea = function(){};
 - }
 
這種方式的缺點(diǎn)是子類(lèi)的實(shí)例對(duì)象用instanceof檢查父類(lèi)時(shí)總是false。這與java中繼承"is a "的關(guān)系是違背的。
2、原型方式寫(xiě)類(lèi),原型方式繼承
core JS自身的對(duì)象系統(tǒng)就是采用原型方式(prototype based)繼承的?;蛘哒f(shuō)core JS沒(méi)有采用常見(jiàn)的類(lèi)繼承(class based)系統(tǒng),而是使用原型繼承來(lái)實(shí)現(xiàn)自己的對(duì)象系統(tǒng)。工作中我們也可以用原型方式來(lái)實(shí)現(xiàn)繼承,代碼復(fù)用以構(gòu)建自己的功能模塊。
- /**
 - * 父類(lèi)Polygon:多邊形
 - *
 - */
 - function Polygon() {}
 - Polygon.prototype.sides = 0;
 - Polygon.prototype.setSides = function(s) {this.sides=s;}
 - /**
 - * 子類(lèi)Triangle:三角形
 - */
 - function Triangle() {}
 - Triangle.prototype = new Polygon(); //這是原型繼承關(guān)鍵的一句
 - Triangle.prototype.getArea = function(){}
 - //new個(gè)對(duì)象
 - var tri = new Triangle();
 - console.log(tri.sides);//繼承的屬性
 - console.log(tri.setSides);//繼承的方法
 - console.log(tri.getArea);//自有方法
 - //instanceof測(cè)試
 - console.log(tri instanceof Triangle);//true,表明該對(duì)象是三角形
 - console.log(tri instanceof Polygon);//true,表明三角形也是多邊形
 
雖然從輸出可以看出子類(lèi)繼承了父類(lèi)Polygon的屬性sides和方法setSides,但sides是0,怎么會(huì)是三角形呢。還得調(diào)用下tri.setSides(3)使之成為三角形。這樣似乎很不方便。不能傳參數(shù),即是原型方式的缺點(diǎn)。優(yōu)點(diǎn)是正確的維護(hù)了"is a"的關(guān)系。
3、組合構(gòu)造函數(shù)/原型方式寫(xiě)類(lèi),采用前面種方式繼承
這種方式父類(lèi),子類(lèi)的屬性都掛在構(gòu)造函數(shù)里,方法都掛在原型上。
- /**
 - * 父類(lèi)Polygon:多邊形
 - */
 - function Polygon(sides) {
 - this.sides = sides;
 - }
 - Polygon.prototype.setSides = function(s) {this.sides=s;}
 - /**
 - * Triangle 三角形
 - * @param {Object} base 底
 - * @param {Object} height 高
 - */
 - function Triangle(base,height) {
 - Polygon.call(this,3);//復(fù)制父類(lèi)屬性給自己
 - this.base = base;
 - this.height = height;
 - }
 - Triangle.prototype = new Polygon();//復(fù)制父類(lèi)方法給自己
 - Triangle.prototype.getArea = function(){ //***定義自己的方法
 - return this.base*this.height/2;
 - }
 - //new個(gè)對(duì)象
 - var tri = new Triangle(12,4);
 - console.log(tri.sides);//繼承的屬性
 - console.log(tri.setSides);//繼承的方法
 - console.log(tri.base);//自有屬性
 - console.log(tri.height);//自有屬性
 - console.log(tri.getArea);//自有方法
 - //instanceof測(cè)試,表明正確的維護(hù)了"is a"的關(guān)系
 - console.log(tri instanceof Triangle);//true,表明該對(duì)象是三角形
 - console.log(tri instanceof Polygon);//true,表明三角形也是多邊形
 
#p#
這篇開(kāi)始寫(xiě)幾個(gè)工具函數(shù)實(shí)現(xiàn)類(lèi)的擴(kuò)展。每個(gè)工具函數(shù)都是針對(duì)特定的寫(xiě)類(lèi)方式(習(xí)慣)。這篇按照構(gòu)造函數(shù)方式寫(xiě)類(lèi):屬性(字段)和方法都掛在this上。以下分別提供了個(gè)類(lèi),分別作為父類(lèi)和子類(lèi)。
- // 父類(lèi)Person
 - function Person(nationality) {
 - this.nationality = nationality;
 - this.setNationality = function(n) {this.nationality=n;};
 - this.getNationality = function() {return this.nationality;};
 - }
 - // 類(lèi)Man
 - function Man(name) {
 - this.name = name;
 - this.setName = function(n){this.name=n;};
 - this.getName = function(){return this.name;};
 - }
 
繼承工具函數(shù)一
- /**
 - * @param {Function} subCls 子類(lèi)
 - * @param {Function} superCls 父類(lèi)
 - * @param {Object} param 父類(lèi)構(gòu)造參數(shù)
 - */
 - function extend(subCls,superCls,param) {
 - superCls.call(subCls.prototype,param);
 - }
 
使用如下
- extend(Man,Person,'China');
 - var m = new Man('jack');
 - console.log(m.nationality);//China
 - console.log(m.setNationality('Japan'));
 - console.log(m.getNationality('Japan'));//Japan
 
輸出可以看到Man繼承了Person的屬性及所有方法。這種繼承方式于java的很不一樣哦,
- class Animal {
 - int legs;
 - Animal(int l) {
 - legs = l;
 - }
 - int getLegs() {
 - return legs;
 - }
 - }
 - public class Person extends Animal{
 - //屬性(字段)
 - String name;
 - //構(gòu)造方法(函數(shù))
 - Person(int legs, String name) {
 - super(legs);//調(diào)用父類(lèi)構(gòu)造器
 - this.name = name;
 - }
 - //方法
 - String getName() {
 - return this.name;
 - }
 - public static void main(String[] args) {
 - Person p = new Person(2,"jack");
 - System.out.println(p.legs);
 - }
 - }
 
Java中,子類(lèi)Person在自身構(gòu)造方法中調(diào)用父類(lèi)構(gòu)造方法super(legs),創(chuàng)建對(duì)象的時(shí)候直接將父類(lèi)構(gòu)造參數(shù)legs:2傳進(jìn)去,不僅僅只傳自己的name:jack。上面JavaScript繼承是在extend時(shí)傳父類(lèi)構(gòu)造參數(shù)(extend函數(shù)的第三個(gè)參數(shù)),而不是在new Man時(shí)將父類(lèi)構(gòu)造參數(shù)傳過(guò)去。好,模擬Java來(lái)實(shí)現(xiàn)下extend,這里巧妙的在子類(lèi)上暫存了父類(lèi)引用。
繼承工具函數(shù)二
- /**
 - * @param {Function} subCls
 - * @param {Function} superCls
 - */
 - function extend(subCls,superCls) {
 - subCls.supr = superCls;
 - }
 
還是以Person為父類(lèi),來(lái)實(shí)現(xiàn)子類(lèi)Woman
- function Woman(nationality,name) {
 - Woman.supr.call(this,nationality);//和java有點(diǎn)類(lèi)似哦,在子類(lèi)中調(diào)用父類(lèi)構(gòu)造器
 - this.name = name;
 - this.setName = function(n){this.name=n;};
 - this.getName = function(){return this.name;};
 - }<br>extend(Woman,Person);<br>
 
***,創(chuàng)建對(duì)象的方式和java也類(lèi)似,即new的時(shí)候同時(shí)將父類(lèi)構(gòu)造參數(shù)(nationality:Japan)傳進(jìn)去。
- var w = new Woman('Japan','lily');
 - console.log(w.nationality);//Japan
 - w.setNationality('U.S.A');
 - console.log(w.getNationality());//U.S.A
 
繼承工具函數(shù)三
- /**
 - * @param {Function} subCls
 - * @param {Function} superCls
 - */
 - function extend(subCls,superCls) {
 - subCls.prototype = new superCls();
 - }
 
父類(lèi),按原型方式寫(xiě),即屬性和方法都掛在原型上。
- /**
 - * 父類(lèi)Person
 - */
 - function Person(){}
 - Person.prototype.nationality = 'China';
 - Person.prototype.getNationality = function() {return this.nationality;}
 - Person.prototype.setNationality = function(n) { this.nationality = n;}
 
子類(lèi)繼承與父類(lèi)
- function Man() {}
 - extend(Man,Person);
 
繼承父類(lèi)的屬性和方法后,再添加子類(lèi)自有屬性,方法
- Man.prototype.name = 'jack';
 - Man.prototype.getName = function() { return this.name;}
 - Man.prototype.setName = function(n) { this.name=n;}
 
測(cè)試如下,
- var m = new Man();
 - console.log(m);
 - console.log(m instanceof Person);
 
可以看到這種寫(xiě)類(lèi)方式,繼承方式完全采用原型機(jī)制。
#p#
繼承工具函數(shù)四
這種方式是目前比較流行的,51ditu網(wǎng)站的開(kāi)發(fā)就是按照這種模式的。
- /**
 - * @param {Function} subCls 子類(lèi)
 - * @param {Function} superCls 父類(lèi)
 - */
 - function extend(subCls,superCls) {
 - //暫存子類(lèi)原型
 - var sbp = subCls.prototype;
 - //重寫(xiě)子類(lèi)原型--原型繼承
 - subCls.prototype = new superCls();
 - //重寫(xiě)后一定要將constructor指回subCls
 - subCls.prototype.constructor = subCls;
 - //還原子類(lèi)原型
 - for(var atr in sbp) {
 - subCls.prototype[atr] = sbp[atr];
 - }
 - //暫存父類(lèi)
 - subCls.supr = superCls;
 - }
 
按 構(gòu)造函數(shù)+原型 方式寫(xiě)類(lèi),即屬性掛在this上,方法掛在prototype上。
- /**
 - * 父類(lèi)Person
 - */
 - function Person(nationality){
 - this.nationality = nationality;
 - }
 - Person.prototype.getNationality = function() {return this.nationality;}
 - Person.prototype.setNationality = function(n) { this.nationality = n;}
 - /**
 - * 子類(lèi)Man
 - */
 - function Man(nationality,name) {
 - Man.supr.call(this,nationality); //很重要的一句,調(diào)用父類(lèi)構(gòu)造器
 - this.name = name;
 - }
 - Man.prototype.getName = function() {return this.name;}
 - Man.prototype.setName = function(n) {this.name=n;}
 
注意子類(lèi)Man中要顯示的調(diào)用父類(lèi)構(gòu)造器已完成父類(lèi)的屬性/字段拷貝。
extend調(diào)用,創(chuàng)建Man的實(shí)例
- extend(Man,Person);
 - var m = new Man('USA','jack');
 - console.log(m);
 - m.setName('lily');
 - console.log(m.name);
 
繼承工具函數(shù)五
- /**
 - * @param {String} className
 - * @param {String/Function} superClass
 - * @param {Function} classImp
 - */
 - function $class(className, superClass, classImp){
 - if(superClass === "") superClass = Object;
 - var clazz = function(){
 - return function(){
 - if(typeof this.init == "function"){
 - this.init.apply(this, arguments);
 - }
 - };
 - }();
 - var p = clazz.prototype = new superClass();
 - var _super = superClass.prototype;
 - window[className] = clazz;
 - classImp.apply(p, [_super]);
 - }
 
定義父類(lèi)Person
- /**
 - * 父類(lèi) Person
 - */
 - $class('Person','',function(){
 - this.init = function(name){
 - this.name = name;
 - };
 - this.getName = function(){
 - return this.name;
 - };
 - this.setName = function(name){
 - this.name = name;
 - }
 - });
 
子類(lèi)Man
- /**
 - * 子類(lèi) Man
 - */
 - $class('Man', Person, function(supr){
 - this.init = function(name, age){
 - supr.init.apply(this,[name]); // 該句很重要
 - this.age = age;
 - };
 - this.getAge = function(){
 - return this.age;
 - };
 - this.setAge = function(age){
 - this.age = age;
 - };
 - });
 - var m = new Man('Jack',25);
 - console.log(m.name); // Jack
 - console.log(m.age); // 25
 
從輸出看可以看到子類(lèi)Man的確繼承了父類(lèi)的屬性和方法。
原文鏈接:http://www.cnblogs.com/snandy/archive/2011/03/09/1977804.html
【編輯推薦】















 
 
 

 
 
 
 