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