說好的講解JavaScript繼承,可是遲到現在講解。廢話不多說,直接進入正題。
既然你想了解繼承,證明你對JavaScript物件導向已經有一定的了解,如還有什麼不理解的可以參考《物件導向JS基礎講解,工廠模式、建構函式模式、原型模式、混合模式、動態原型模式》,接下來講一般透過那些方法完成JavaScript的繼承。
原型鏈
JavaScript中實現繼承最簡單的方式就是使用原型鏈,將子類型的原型指向父類型的實例即可,即“子類型.prototype = new 父類型();”,實現方法如下:
// 为父类型创建构造函数 function SuperType() { this.name = ['wuyuchang', 'Jack', 'Tim']; this.property = true; } // 为父类型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 为子类型创建构造函数 function SubType() { this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } // 实现继承的关键步骤,子类型的原型指向父类型的实例 SubType.prototype = new SuperType(); // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下为测试代码示例 */ var instance1 = new SubType(); instance1.name.push('wyc'); instance1.test.push('h5'); alert(instance1.getSuerperValue()); // true alert(instance1.getSubValue()); // false alert(instance1.name); // wuyuchang,Jack,Tim,wyc alert(instance1.test); // h1,h2,h3,h4,h5 var instance2 = new SubType(); alert(instance2.name); // wuyuchang,Jack,Tim,wyc alert(instance2.test); // h1,h2,h3,h4
可以看到如上的程式碼就是透過原型鏈實現的一個簡單的繼承,但看到測試程式碼範例中還是存在一些問題。相信看了我的博文《物件導向JS基礎講解,工廠模式、建構函式模式、原型模式、混合模式、動態原型模式》的童鞋一定知道原型鏈程式碼存在的第一個問題是由於子類型的原型是父類型的實例,也就是子類型的原型中包含的父類型的屬性,從而導致引用類型值的原型屬性會被所有實例所共享。以上程式碼的instance1.name.push('wyc');就可以證明此問題的存在。而原型鏈的第二個問題就是:在建立子類型的實例時,不能傳遞參數給超類型的建構子。因此我們在實際開發中,很少單獨使用原型鏈。
借用建構子
為了解決原型鏈中存在的兩個問題,開發人員開始使用一種稱為借用構造函數的技術來解決原型鏈中存在的問題。這種技術的實作想法也挺簡單,只需要在子類型的建構子內呼叫父類型的建構子。別忘了,函數只不過是在特定環境中執行程式碼的對象,因此可以透過apply()或call()方法執行建構子。程式碼如下:
// 为父类型创建构造函数 function SuperType(name) { this.name = name; this.color = ['pink', 'yellow']; this.property = true; this.testFun = function() { alert('http://tools.jb51.net/'); } } // 为父类型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 为子类型创建构造函数 function SubType(name) { SuperType.call(this, name); this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下为测试代码示例 */ var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); instance1.name.push('hello'); instance1.test.push('h5'); instance1.color.push('blue'); instance1.testFun(); // http://tools.jb51.net/ alert(instance1.name); // wuyuchang,Jack,Nick,hello // alert(instance1.getSuerperValue()); // error 报错 alert(instance1.test); // h1,h2,h3,h4,h5 alert(instance1.getSubValue()); // false alert(instance1.color); // pink,yellow,blue var instance2 = new SubType('wyc'); instance2.testFun(); // http://tools.jb51.net/ alert(instance2.name); // wyc // alert(instance2.getSuerperValue()); // error 报错 alert(instance2.test); // h1,h2,h3,h4 alert(instance2.getSubValue()); // false alert(instance2.color); // pink,yellow
可以看到以上程式碼中子型別SubType的建構函式內透過呼叫父型別"SuperType.call(this, name);",從而實現了屬性的繼承,也可以在子型別建立實例的時候為父類型傳遞參數了,但新的問題又來了。我可以看到我在父型別的建構子中定義了一個方法:testFun,在父型別的原型中定義了一個方法:getSuperValue。可是在實例化子型別後還是無法呼叫父型別的原型中定義的方法getSuperValue,只能呼叫父型別中建構函式的方法:testFun。這就同創建物件中只使用建構函式模式一樣,使得函數沒有復用性可言。考慮到這些問題,借用構造函數的技術也是很少單獨使用的。
組合繼承(原型鏈 借用建構子)
顧名思義,組合繼承就是結合使用原型鏈與借用建構函式的優點,組合而成的一個模式。實作也很簡單,既然是結合,那當然結合了兩方的優點,即原型鏈繼承方法,而在構造函數繼承屬性。具體程式碼實作如下:
// 为父类型创建构造函数 function SuperType(name) { this.name = name; this.color = ['pink', 'yellow']; this.property = true; this.testFun = function() { alert('http://tools.jb51.net/'); } } // 为父类型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 为子类型创建构造函数 function SubType(name) { SuperType.call(this, name); this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } SubType.prototype = new SuperType(); // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下为测试代码示例 */ var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); instance1.name.push('hello'); instance1.test.push('h5'); instance1.color.push('blue'); instance1.testFun(); // http://tools.jb51.net/ alert(instance1.name); // wuyuchang,Jack,Nick,hello alert(instance1.getSuerperValue()); // true alert(instance1.test); // h1,h2,h3,h4,h5 alert(instance1.getSubValue()); // false alert(instance1.color); // pink,yellow,blue var instance2 = new SubType('wyc'); instance2.testFun(); // http://tools.jb51.net/ alert(instance2.name); // wyc alert(instance2.getSuerperValue()); // true alert(instance2.test); // h1,h2,h3,h4 alert(instance2.getSubValue()); // false alert(instance2.color); // pink,yellow
以上程式碼透過SuperType.call(this, name);繼承父類型的屬性,透過SubType.prototype = new SuperType();繼承父類型的方法。以上程式碼很方便的解決了原型鏈與借用建構函數所遇到的問題,成為了JavaScript中最常用的實例繼承的方法。但混合模式也並非沒有缺點,可以看到在以上程式碼中在繼承方法的時候實際上已經繼承了父類型的屬性,只不過此時對於引用型別屬於共享的,因此在子類型的建構函式內在次調用父型別的建構子因而繼承了父型別的屬性而去覆寫了原型中所繼承的屬性,這樣呼叫兩次建構子顯然沒有必要,但有什麼方法可以解呢?解決此問題時先看以下兩個模式。
原型式繼承
原型式繼承的的實現方法與普通繼承的實現方法不同,原型式繼承並沒有使用嚴格意義上的構造函數,而是藉助原型可以基於已有的對象創建新對象,同時還不必因此創建自訂類型。具體程式碼如下:
function object(o) { function F() {} F.prototype = o; return new F(); }
程式碼範例:
/* 原型式继承 */ function object(o) { function F() {} F.prototype = o; return new F(); } var person = { name : 'wuyuchang', friends : ['wyc', 'Nicholas', 'Tim'] } var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Bob'); var anotherPerson2 = object(person); anotherPerson2.name = 'Jack'; anotherPerson2.friends.push('Rose'); alert(person.friends); // wyc,Nicholas,Tim,Bob,Rose
寄生式繼承
/* 寄生式继承 */ function createAnother(original) { var clone = object(original); clone.sayHi = function() { alert('hi'); } return clone; }
使用範例:
/* 原型式继承 */ function object(o) { function F() {} F.prototype = o; return new F(); } /* 寄生式继承 */ function createAnother(original) { var clone = object(original); clone.sayHi = function() { alert('hi'); } return clone; } var person = { name : 'wuyuchang', friends : ['wyc', 'Nicholas', 'Rose'] } var anotherPerson = createAnother(person); anotherPerson.sayHi();
寄生组合式继承
前面说过了JavaScrip中组合模式实现继承的缺点,现在我们就来解决它的缺点,实现思路是,对于构造函数继承属性,而原型链的混成形式继承方法,即不用在继承方法的时候实例化父类型的构造函数。代码如下:
function object(o) { function F() {} F.prototype = o; return new F(); } /* 寄生组合式继承 */ function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
而在使用时只需要将组合模式中的“SubType.prototype = new SuperType();”这行代码替换成inheritPrototype(subType, superType);即可。寄生组合式继承的高效率体现在它只调用了一次父类型构造函数,避免了创建不必要的或多余的属性。与此同时,原型链还能保持不变,因此,还能够正常使用instanceof和isPrototypeof()。这也是目前来说最理想的继承方式了,目前也在向这种模式转型。(YUI也使用了这种模式。)
此博文参考《JavaScript高级程序设计第3版》,代码为经过改写,更具体,并加了注释使大家更易懂。如对JS继承方面有独到见解的童鞋不别吝啬,回复您的见解供大家参考!