Home > Article > Web Front-end > Detailed explanation of JavaScript object-oriented inheritance_javascript skills
一、面相物件繼承機制
這個實例使用UML很好的解釋了繼承機制。
說明繼承機制最簡單的方式是,利用一個經典的例子就是幾何形狀。實際上,幾何形狀只有兩種,即橢圓形(是圓形的)和多邊形(具有一定數量的邊)。圓是橢圓的一種,它只有一個焦點。三角形、矩形和五邊形都是多邊形的一種,有不同數量的邊。正方形是長方形的一種,所有的邊等長。這就構成了一種完美的繼承關係,很好的解釋了物件導向的繼承機制。
在這個例子中,形狀是橢圓形和多邊形的基底類別(通常我們也可以叫它父類,所有類別都由它繼承而來)。橢圓具有一個屬(foci),說明橢圓具有的焦點的個數。圓形繼承了橢圓形,因此圓形是橢圓形的子類,橢圓形是圓形的超類。同樣,三角形、矩形和五邊形都是多邊形的子類,多邊形是它們的超類。最後,正方形繼承了長方形。
最好用圖來解釋這種繼承關係,這是 UML(統一建模語言)的用武之地。 UML的主要用途之一是,視覺化地表示像繼承這樣的複雜物件關係。下面的圖示是解釋形狀和它的子類別之間關係的UML圖示:
在UML中,每個方框表示一個類別,由類別名稱說明。三角形 、長方形和五邊形頂部的線段匯集在一起,指向形狀,說明這些類別都是由形狀繼承而來。同樣,從正方形指向矩形的箭頭說明了它們之間的繼承關係。
二、ECMAScript繼承機制的實作
要以ECMAScript實作繼承機制,您可以從要繼承的基底類別著手。所有開發者定義的類別都可作為基底類別。出於安全原因,本機類別和宿主類別不能作為基類,這樣可以防止公用存取編譯過的瀏覽器級的程式碼,因為這些程式碼可以被用於惡意攻擊。
選定基類後,就可以創建它的子類別了。是否使用基類完全由你決定。有時,你可能會想要建立一個不能直接使用的基底類,它只是用來給子類別通用的函數。在這種情況下,基底類別被看作抽象類別。儘管ECMAScript並沒有像其他語言那樣嚴格地定義抽象類,但有時它的確會創建一些不允許使用的類別。通常,我們稱這種類別為抽象類別。
所建立的子類別將繼承超類別的所有屬性與方法,包括建構函式及方法的實作。記住,所有屬性和方法都是公用的,因此子類別可直接存取這些方法。子類別還可新增超類別中沒有的新屬性和方法,也可以覆寫超類別的屬性和方法。由於JS並不是正統的物件導向語言,有些名詞也需要做出改變。
三、ECMAScript繼承的方式
ECMAScript語言中將被繼承的類別(基底類別)稱為超類型,子類別(或衍生類別)稱為子類型。和其他功能一樣,ECMAScript實現繼承的方式不只一種。這是因為JavaScript中的繼承機制並不是明確規定的,而是透過模仿來實現的。這意味著所有的繼承細節並非完全由解釋程序處理。身為開發者,你有權決定最適用的繼承方式。以下為您介紹幾種具體的繼承方式。
(1)原型鏈方式
繼承此形式在ECMAScript中原本是用於原型鏈的。上一篇部落格文章已經介紹了創建物件的原型方式。原型鏈擴展了這種方式,以有趣的方式實現繼承機制。 prototype 物件是個模板,要實例化的物件都以這個模板為基礎。總而言之,prototype 物件的任何屬性和方法都被傳遞給那個類別的所有實例。原型鏈利用這種功能來實現繼承機制。我們來看一個例子:
function A() {//超类型A中必须没有参数 this.color = "red"; this.showColor = function () { return this.color; }; }; function B() {//子类型B this.name = "John"; this.showName = function () { return this.name; }; }; B.prototype = new A();//子类型B继承了超类型A,通过原型,形成链条 var a = new A(); var b = new B(); document.write(a.showColor());//输出:blue document.write(b.showColor());//输出:red document.write(b.showName());//输出:John
在原型鏈中,instanceof運算子的運作方式也很獨特。所有對B的實例,instanceof為A和B都傳回true。 ECMAScript的弱型世界中,這是極為有用的工具,不過使用物件冒充時不能使用它。例如:
var b = new B(); document.write(b instanceof A);//输出:true document.write(b instanceof B);//输出:true
使用原型链方式实现了继承,但是这种方式无法共享和子类型给超类型传递参数。我们可以借用构造函数方式(也就是对像冒充)的方式来解决这两个问题。
(2)对象冒充方式
对象冒充方式的其原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用对象声明的构造函数方式)。因为构造函数只是一个函数,所以可使A构造函数成为B的方法,然后调用它。B就会收到A的构造函数中定义的属性和方法。例如,用下面的方式改写上面的例子创建对象A和B:
call()方法
function A(Color) {//创建超类型A this.color = Color; this.showColor = function () { return this.color; }; }; function B(Color,Name) {//创建子类型B A.call(this, Color);//对象冒充,给超类型传参 this.name = Name;//新添加的属性 this.showName = }; var a = new A("blue"); var b = new B("red", "John"); document.write(a.showColor());//输出:blue document.write(b.showColor());//输出:red document.write(b.showName());//输出:John
apply()方法
和上面call()方法唯一的区别就是在子类型B中的代码:
A.call(this,arguments);//对象冒充,给超类型传参
当然,只有超类型中的参数顺序与子类型中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。
使用对象冒充方式虽然解决了共享和传参的问题,但是没有原型,复用就更不可能了,所以我们组合上述的两种方式,即原型链方式和对象冒充的方式实现JS的继承。
(3)混合方式
这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。由于这种混合方式使用了原型链,所以instanceof运算符仍能正确运行。
在上一篇文章,创建对象的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承prototype对象的方法。用这两种方式重写前面的例子,代码如下:
function A(Color) { this.color = Color; }; A.prototype.showColor = function () { return this.color; }; function B(Color, Name) { A.call(this, Color);//对象冒充 this.name = Name; }; B.prototype = new A();//使用原型链继承 B.prototype.showName = function () { return this.name; }; var a = new A("blue"); var b = new B("red", "John"); document.write(a.showColor());//输出:blue document.write(b.showColor());//输出:red document.write(b.showName());//输出:John
继承的方式和创建对象的方式有一定的联系,推荐使用的继承方式还时原型链和对象冒充的混合方式。使用这种混合方式可以避免一些不必要的问题。
看这篇文章的时候,必须看一下前面的创建对象的方式:详解JavaScript基于面向对象之创建对象(1)和详解JavaScript基于面向对象之创建对象(2)。
以上就是本文的全部内容,希望对大家的学习有所帮助。