055_继承机制
1. 繼承機(jī)制實(shí)例
1.1. 說明繼承機(jī)制最簡單的方式是, 利用一個經(jīng)典的例子:?幾何形狀。實(shí)際上, 幾何形狀只有兩種, 即橢圓形和多邊形。圓是橢圓的一種, 它只有一個焦點(diǎn)。三角形、矩形和五邊形都是多邊形的一種, 具有不同數(shù)量的邊。正方形是矩形的一種, 所有的邊等長。這就構(gòu)成了一種完美的繼承關(guān)系。
1.2. 在這個例子中, 形狀(Shape)是橢圓形(Ellipse)和多邊形(Polygon)的基類(base class所有類都由它繼承而來)。圓形(Circle)繼承了橢圓形, 因此圓形是橢圓形的子類(subclass), 橢圓形是圓形的超類(superclass)。同樣, 三角形(Triangle)、矩形(Rectangle)和五邊形(Pentagon)都是多邊形的子類, 多邊形是它們的超類。最后, 正方形(Square)繼承了矩形。
2. 繼承機(jī)制的實(shí)現(xiàn)
2.1. 要用JavaScript實(shí)現(xiàn)繼承機(jī)制, 您可以從要繼承的基類入手。所有開發(fā)者定義的類都可作為基類。出于安全原因, 本地類和宿主類不能作為基類, 這樣可以防止公用訪問編譯過的瀏覽器級的代碼, 因?yàn)檫@些代碼可以被用于惡意攻擊。
2.2. 選定基類后, 就可以創(chuàng)建它的子類了。 有時, 你可能想創(chuàng)建一個不能直接使用的基類, 它只是用于給子類提供通用的函數(shù), 在這種情況下, 基類被看作抽象類。
2.3. 創(chuàng)建的子類將繼承超類的所有屬性和方法, 包括構(gòu)造函數(shù)及方法的實(shí)現(xiàn)。記住, 所有屬性和方法都是公用的, 因此子類可直接訪問這些方法。子類還可添加超類中沒有的新屬性和方法, 也可以覆蓋超類的屬性和方法。
3. 對象冒充
3.1. 其原理如下: 構(gòu)造函數(shù)使用this關(guān)鍵字給所有屬性和方法賦值(即采用類聲明的構(gòu)造函數(shù)方式)。因?yàn)闃?gòu)造函數(shù)只是一個函數(shù), 所以可使ClassA構(gòu)造函數(shù)成為ClassB的方法, 然后調(diào)用它。ClassB就會收到ClassA 的構(gòu)造函數(shù)中定義的屬性和方法。例如, 用下面的方式定義ClassA和ClassB:
function ClassA(color) {this.color = color;this.sayColor = function () {alert(this.color);}; }function ClassB(color) {}3.2. 這個原理是把ClassA作為常規(guī)函數(shù)來建立繼承機(jī)制, 而不是作為構(gòu)造函數(shù)。如下使用構(gòu)造函數(shù)ClassB可以實(shí)現(xiàn)繼承機(jī)制:
function ClassB(color) {this.newMethod = ClassA;this.newMethod(color);delete this.newMethod; }3.3. 在這段代碼中, 為ClassA賦予了方法newMethod。然后調(diào)用該方法, 傳遞給它的是ClassB構(gòu)造函數(shù)的參數(shù)color。最后一行代碼刪除了對ClassA的引用, 這樣以后就不能再調(diào)用它。
3.4. 所有新屬性和新方法都必須在刪除了新方法的代碼行后定義。否則, 可能會被超類的相關(guān)屬性和方法覆蓋:
function ClassB(color, name) {this.newMethod = ClassA;this.newMethod(color);delete this.newMethod;this.name = name;this.sayName = function () {alert(this.name);}; }3.5. 對象冒充可以實(shí)現(xiàn)多重繼承
3.5.1. 有趣的是, 對象冒充可以支持多重繼承。也就是說, 一個類可以繼承多個超類。用UML表示的多重繼承機(jī)制如下圖所示:
3.5.2. 例如, 如果存在兩個類ClassX和ClassY, ClassZ想繼承這兩個類, 可以使用下面的代碼:
function ClassZ() {this.newMethod = ClassX;this.newMethod();delete this.newMethod;this.newMethod = ClassY;this.newMethod();delete this.newMethod; }3.5.3. 這里存在一個弊端, 如果存在兩個類ClassX和ClassY具有同名的屬性或方法, ClassY具有高優(yōu)先級。因?yàn)樗鼜暮竺娴念惱^承。除這點(diǎn)小問題之外, 用對象冒充實(shí)現(xiàn)多重繼承機(jī)制輕而易舉。
4. call()方法
4.1. call()方法是與經(jīng)典的對象冒充方法最相似的方法。它的第一個參數(shù)用作this的對象。其他參數(shù)都直接傳遞給函數(shù)自身。例如:
function sayColor(prefix, suffix) {alert(prefix + this.color + suffix); };var obj = new Object(); obj.color = "blue";sayColor.call(obj, "The color is ", "a very nice color indeed.");4.2. 在這個例子中, 函數(shù)sayColor()在對象外定義, 即使它不屬于任何對象, 也可以引用關(guān)鍵字this。對象obj的color屬性等于blue。調(diào)用call()方法時, 第一個參數(shù)是obj, 說明應(yīng)該賦予sayColor()函數(shù)中的this 關(guān)鍵字值是obj。第二個和第三個參數(shù)是字符串。它們與sayColor()函數(shù)中的參數(shù)prefix和suffix匹配, 最后生成的消息"The color is blue, a very nice color indeed."將被顯示出來。
4.3. 要與繼承機(jī)制的對象冒充方法一起使用該方法, 只需將前三行的賦值、調(diào)用和刪除代碼替換即可:
function ClassA(color) {this.color = color;this.sayColor = function () {alert(this.color);}; }function ClassB(color, name) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.call(this, color);this.name = name;this.sayName = function () {alert(this.name);}; }4.4. 這里, 我們需要讓ClassA中的關(guān)鍵字this等于新創(chuàng)建的ClassB對象, 因此this是第一個參數(shù)。第二個參數(shù)color對兩個類來說都是唯一的參數(shù)。
5. apply()方法
5.1. apply()方法有兩個參數(shù), 用作this的對象和要傳遞給函數(shù)的參數(shù)的數(shù)組。例如:
function sayColor(prefix, suffix) {alert(prefix + this.color + suffix); };var obj = new Object(); obj.color = "blue";sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));5.2. 這個例子與前面的例子相同, 只是現(xiàn)在調(diào)用的是apply()方法。調(diào)用apply()方法時, 第一個參數(shù)仍是obj, 說明應(yīng)該賦予sayColor()函數(shù)中的this關(guān)鍵字值是obj。第二個參數(shù)是由兩個字符串構(gòu)成的數(shù)組, 與sayColor() 函數(shù)中的參數(shù)prefix和suffix匹配, 最后生成的消息仍是"The color is blue, a very nice color indeed.", 將被顯示出來。
5.3. 該方法也用于替換前三行的賦值、調(diào)用和刪除新方法的代碼:
function ClassB(color, name) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.apply(this, new Array(color));this.name = name;this.sayName = function () {alert(this.name);}; }5.4. 同樣的, 第一個參數(shù)仍是this, 第二個參數(shù)是只有一個值color的數(shù)組。可以把ClassB的整個arguments對象作為第二個參數(shù)傳遞給apply()方法:
function ClassB(color, name) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.apply(this, arguments);this.name = name;this.sayName = function () {alert(this.name);}; }5.5. 當(dāng)然, 只有超類中的參數(shù)順序與子類中的參數(shù)順序完全一致時才可以傳遞參數(shù)對象。如果不是, 就必須創(chuàng)建一個單獨(dú)的數(shù)組, 按照正確的順序放置參數(shù)。
6. 原型鏈(prototype chaining)
6.1. prototype對象是個模板, 要實(shí)例化的對象都以這個模板為基礎(chǔ)。總而言之, prototype對象的任何屬性和方法都被傳遞給那個類的所有實(shí)例。原型鏈利用這種功能來實(shí)現(xiàn)繼承機(jī)制。
6.2. 如果用原型方式重定義前面例子中的類, 它們將變?yōu)橄铝行问?
function ClassA() {}ClassA.prototype.color = "blue"; ClassA.prototype.sayColor = function () {alert(this.color); };function ClassB() {}ClassB.prototype = new ClassA();6.3. 這里, 把ClassB的prototype屬性設(shè)置成ClassA的實(shí)例。
6.4. 調(diào)用ClassA的構(gòu)造函數(shù), 沒有給它傳遞參數(shù)。這在原型鏈中是標(biāo)準(zhǔn)做法。要確保構(gòu)造函數(shù)沒有任何參數(shù)。
6.5. 與對象冒充相似, 子類的所有屬性和方法都必須出現(xiàn)在prototype屬性被賦值后, 因?yàn)樵谒百x值的所有方法都會被刪除。為什么?因?yàn)閜rototype屬性被替換成了新對象, 添加了新方法的原始對象將被銷毀。所以, 為 ClassB類添加name屬性和sayName()方法的代碼如下:
function ClassB() {}ClassB.prototype = new ClassA();ClassB.prototype.name = ""; ClassB.prototype.sayName = function () {alert(this.name); };6.6. 原型鏈的弊端是不支持多重繼承。記住, 原型鏈會用另一類型的對象重寫類的prototype屬性。
7. 對象冒充和原型鏈
7.1. 對象冒充的主要問題是必須使用構(gòu)造函數(shù)方式, 構(gòu)造函數(shù)會重復(fù)生成函數(shù)。不過如果使用原型鏈, 不能通過給構(gòu)造函數(shù)傳遞參數(shù)來初始化屬性的值, 而且對象被多個實(shí)例共享。
7.2. 繼承機(jī)制, 用對象冒充繼承構(gòu)造函數(shù)的屬性, 用原型鏈繼承prototype對象的方法。用這兩種方式重寫前面的例子, 代碼如下:
function ClassA(color) {this.color = color; }ClassA.prototype.sayColor = function () {alert(this.color); };function ClassB(color, name) {ClassA.call(this, color);this.name = name; }ClassB.prototype = new ClassA();ClassB.prototype.sayName = function () {alert(this.name); };7.3. 在此例子中, 在ClassB構(gòu)造函數(shù)中, 用對象冒充繼承ClassA類的color屬性。用原型鏈繼承ClassA類的方法。
7.4. 由于這種混合方式使用了原型鏈, 所以instanceof運(yùn)算符仍能正確運(yùn)行。
8. 對象冒充例子
8.1. 代碼
<!DOCTYPE html> <html lang="zh-CN"><head><meta charset="utf-8" /><title>對象冒充實(shí)現(xiàn)繼承</title></head><body><script type="text/javascript">function Polygon(myName) { // 多邊形this.myName = myName;}function Rectangle(myName, width, height) { // 矩形this.extendPolygon = Polygon;this.extendPolygon(myName);delete this.extendPolygon;this.width = width;this.height = height;this.perimeter = function() {return (this.width + this.height) * 2;}}function Triangle(myName, width, height) { // 三角形Polygon.call(this, myName);this.width = width;this.height = height;this.area = function() {return this.width * this.height / 2;}}function Square(myName, width) { // 正方形Rectangle.apply(this, [myName, width, width]);this.area = function() {return this.width * this.width;}}var rectangle = new Rectangle('矩形', 100, 30);document.write(rectangle.myName + '的周長: ' + rectangle.perimeter() + '<br />');var square = new Square('正方形', 100);document.write(square.myName + '的周長: ' + square.perimeter() + '<br />');document.write(square.myName + '的面積: ' + square.area() + '<br />');var triangle = new Triangle('三角形', 100, 30);document.write(triangle.myName + '的面積: ' + triangle.area() + '<br />');document.write((rectangle instanceof Polygon) + '<br />');document.write((square instanceof Polygon) + '<br />');document.write((triangle instanceof Polygon) + '<br />');</script></body> </html>8.2. 效果圖
9. 原型鏈例子
9.1. 代碼
<!DOCTYPE html> <html lang="zh-CN"><head><meta charset="utf-8" /><title>原型鏈實(shí)現(xiàn)繼承</title></head><body><script type="text/javascript">function ClassA() {}ClassA.prototype.color = "blue";ClassA.prototype.sayColor = function () {document.write(this.color + '<br />');};function ClassB(){}ClassB.prototype = new ClassA();var personZhang = new ClassB();personZhang.sayColor();function ClassC(firstName, lastName){this.firstName = firstName;this.lastName = lastName;}ClassC.prototype.fullName = function() {return this.firstName + this.lastName;};function ClassD(firstName, lastName){ClassC.apply(this, [firstName, lastName]);}ClassD.prototype = new ClassC();ClassD.prototype.drink = function() {document.write(this.fullName() + '喝的多。' + '<br />');};var personLi = new ClassD('李', '四');personLi.drink();document.write((personLi instanceof ClassD) + '<br />');document.write((personLi instanceof ClassC) + '<br />');</script></body> </html>9.2. 效果圖
總結(jié)
- 上一篇: 044_定义类或对象
- 下一篇: 072_Math对象