<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 2.4 混合對象“類” ### 2.4.1 類理論 類/ 繼承描述了一種代碼的組織結構形式——一種在軟件中對真實世界中問題領域的建模方法。 面向對象編程強調的是數據和操作數據的行為本質上是互相關聯的(當然,不同的數據有不同的行為),因此好的設計就是把數據以及和它相關的行為打包(或者說封裝)起來。這在正式的計算機科學中有時被稱為數據結構。 **關于類、繼承和實例化還有多態的基礎概念認識,略。** **1. “類”設計模式** 你可能從來沒把類作為設計模式來看待,討論得最多的是面向對象設計模式,比如迭代器模式、觀察者模式、工廠模式、單例模式,等等。從這個角度來說,我們似乎是在(低級)面向對象類的基礎上實現了所有(高級)設計模式,似乎面向對象是優秀代碼的基礎。 如果你之前接受過正規的編程教育的話,可能聽說過**過程化編程**,這種代碼只包含過程(函數)調用,沒有高層的抽象。 當然,如果你有**函數式編程**(比如Monad)的經驗就會知道類也是非常常用的一種設計模式。但是對于其他人來說,這可能是第一次知道類并不是必須的編程基礎,而是一種可選的代碼抽象。 有些語言(比如Java)并不會給你選擇的機會,類并不是可選的——萬物皆是類。其他語言(比如C/C++ 或者PHP)會提供過程化和面向類這兩種語法,開發者可以選擇其中一種風格或者混用兩種風格。 **2. JavaScript中的“類”** 在相當長的一段時間里,JavaScript 只有一些近似類的語法元素(比如new 和instanceof),不過在后來的ES6 中新增了一些元素,比如`class `關鍵字。 **但這并不意味著JavaScript中實際上存在類。** 在軟件設計中類是一種可選的模式,雖然JavaScript有近似類的語法,但是JavaScript 的機制似乎一直在阻止你使用類設計模式。在近似類的表象之下,JavaScript 的機制其實和類完全不同。語法糖和( 廣泛使用的)JavaScript“類”庫試圖掩蓋這個現實,但是你遲早會面對它:其他語言中的類和JavaScript中的“類”并不一樣。 ### 2.4.2 類的機制 在許多面向類的語言中,“標準庫”會提供Stack 類,它是一種“棧”數據結構(支持壓入、彈出,等等)。Stack 類內部會有一些變量來存儲數據,同時會提供一些公有的可訪問行為(“方法”),從而讓你的代碼可以和(隱藏的)數據進行交互(比如添加、刪除數據)。 但是在這些語言中,你實際上并不是直接操作Stack(除非創建一個靜態類成員引用)。Stack 類僅僅是一個抽象的表示,它描述了所有“棧”需要做的事,但是它本身并不是一個“棧”。你必須先**實例化Stack 類**然后才能對它進行操作。 **1. 建造** “類”和“實例”的概念來源于房屋建造。 一個類就是一張藍圖。為了獲得真正可以交互的對象,我們必須按照類來建造(也可以說實例化)一個東西,這個東西通常被稱為**實例**,有需要的話,可以直接在實例上調用方法并訪問其所有公有數據屬性。這個對象就是類中描述的所有特性的一份副本。 把類和實例對象之間的關系看作是**直接關系**而不是間接關系通常更有助于理解。類通過**復制操作**被實例化為對象形式: ![](https://box.kancloud.cn/37ba1d3b1fe77606183b41d8147f4839_736x361.png) 箭頭的方向是從左向右、從上向下,它表示概念和物理意義上發生的復制操作。 **2. 構造函數** 類實例是由一個特殊的類方法構造的,這個方法名通常和類名相同,被稱為**構造函數**。這個方法的任務就是**初始化實例需要的所有信息(狀態)。** 舉例來說,思考下面這個關于類的偽代碼(編造出來的語法): ~~~ class CoolGuy { specialTrick = nothing CoolGuy( trick ) { specialTrick = trick } showOff() { output( "Here's my trick: ", specialTrick ) } } ~~~ 我們可以調用類構造函數來生成一個CoolGuy 實例: ~~~ Joe = new CoolGuy( "jumping rope" ) Joe.showOff() // 這是我的絕技:跳繩 ~~~ 注意,CoolGuy 類有一個CoolGuy() 構造函數,執行new CoolGuy() 時實際上調用的就是它。構造函數會返回一個對象(也就是類的一個實例),之后我們可以在這個對象上調用showOff() 方法,來輸出指定CoolGuy 的特長。 類構造函數屬于類,而且通常和類同名。此外,構造函數大多需要用new 來調,這樣語言引擎才知道你想要構造一個新的類實例。 ### 2.4.3 類的繼承 在面向類的語言中,你可以先定義一個類,然后定義一個繼承前者的類。后者通常被稱為“子類”,前者通常被稱為“父類”。 思考下面關于類繼承的偽代碼(省略了構造函數): ~~~ class Vehicle { engines = 1 ignition() { output( "Turning on my engine." ); } drive() { ignition(); output( "Steering and moving forward!" ) } } class Car inherits Vehicle { wheels = 4 drive() { inherited:drive() output( "Rolling on all ", wheels, " wheels!" ) } } class SpeedBoat inherits Vehicle { engines = 2 ignition() { output( "Turning on my ", engines, " engines." ) } pilot() { inherited:drive() output( "Speeding through the water with ease!" ) } } ~~~ 我們通過定義Vehicle 類來假設一種發動機,一種點火方式,一種駕駛方法。接下來我們定義了兩類具體的交通工具:Car 和SpeedBoat。它們都從Vehicle 繼承了通用的特性并根據自身類別修改了某些特性。汽車需要四個輪子,快艇需要兩個發動機,因此它必須啟動兩個發動機的點火裝置。 **1. 多態** Car 重寫了繼承自父類的drive() 方法,但是之后Car 調用了inherited:drive() 方法,這表明Car 可以引用繼承來的原始drive() 方法。快艇的pilot() 方法同樣引用了原始drive() 方法。 這個技術被稱為**多態或者虛擬多態**。在本例中,更恰當的說法是相對多態。 多態是一個非常廣泛的話題,“相對”只是多態的一個方面:任何方法都可以引用繼承層次中高層的方法(無論高層的方法名和當前方法名是否相同)。之所以說“相對”是因為我們并不會定義想要訪問的絕對繼承層次(或者說類),而是使用相對引用 “查找上一層”。 在許多語言中可以使用`super` 來代替本例中的inherited:, 它的含義是**“ 超類”**(superclass),表示當前類的父類/ 祖先類。 多態的另一個方面是,在繼承鏈的不同層次中一個方法名可以被多次定義,當調用方法時會自動選擇合適的定義。在之前的代碼中就有兩個這樣的例子:drive() 被定義在Vehicle 和Car 中,ignition() 被定義在Vehicle 和SpeedBoat 中。 **2. 多重繼承** 有些面向類的語言允許你繼承多個“父類”。多重繼承意味著所有父類的定義都會被復制到子類中。 從表面上來,對于類來說這似乎是一個非常有用的功能,可以把許多功能組合在一起。 然而,這個機制同時也會帶來很多復雜的問題。如果兩個父類中都定義了drive() 方法的話,子類引用的是哪個呢?難道每次都需要手動指定具體父類的drive() 方法嗎?這樣多態繼承的很多優點就存在了。 除此之外,還有一種被稱為**鉆石問題**的變種。在鉆石問題中,子類D 繼承自兩個父類(B和C),這兩個父類都繼承自A。如果A 中有drive() 方法并且B 和C 都重寫了這個方法(多態),那當D 引用drive() 時應當選擇哪個版本呢(B:drive() 還是C:drive())? ![](https://box.kancloud.cn/13e4d2d10e00e738626e0300d4b35b21_736x282.png) ### 2.4.4 混入 在繼承或者實例化時,JavaScript 的對象機制并不會自動執行復制行為。簡單來說,JavaScript 中只有對象,并不存在可以被實例化的“類”。一個對象并不會被復制到其他對象,它們會被**關聯**起來。 由于在其他語言中類表現出來的都是復制行為,因此JavaScript 開發者也想出了一個方法來模擬類的復制行為,這個方法就是**混入**。接下來我們會看到兩種類型的混入:**顯式和隱式**。 **1. 顯示混入** 回顧一下之前提到的Vehicle 和Car。由于JavaScript 不會自動實現Vehicle到Car 的復制行為,所以我們需要手動實現復制功能。這個功能在許多庫和框架中被稱為`extend(..)`,但是為了方便理解我們稱之為mixin(..)。 ~~~ // 非常簡單的mixin(..) 例子: function mixin( sourceObj, targetObj ) { for (var key in sourceObj) { // 只會在不存在的情況下復制 if (!(key in targetObj)) { targetObj[key] = sourceObj[key]; } } return targetObj; } var Vehicle = { engines: 1, ignition: function() { console.log( "Turning on my engine." ); }, drive: function() { this.ignition(); console.log( "Steering and moving forward!" ); } }; var Car = mixin( Vehicle, { wheels: 4, drive: function() { Vehicle.drive.call( this ); console.log( "Rolling on all " + this.wheels + " wheels!" ); } } ); ~~~ 現在Car 中就有了一份Vehicle 屬性和函數的副本了。從技術角度來說,函數實際上沒有被復制,復制的是函數引用。所以,Car 中的屬性ignition 只是從Vehicle 中復制過來的對于ignition() 函數的引用。相反,屬性engines 就是直接從Vehicle 中復制了值1。Car 已經有了drive 屬性(函數),所以這個屬性引用并沒有被mixin 重寫,從而保留了Car 中定義的同名屬性,實現了“子類”對“父類”屬性的重寫. #### (1)再說多態 分析一下這條語句:`Vehicle.drive.call( this )`。這就是**顯式多態**。在之前的偽代碼中對應的語句是`inherited:drive()`,我們稱之為**相對多態**。 JavaScript( 在ES6 之前) 并沒有相對多態的機制。所以, 由于Car 和Vehicle 中都有drive() 函數,為了指明調用對象,必須使用絕對(而不是相對)引用。我們通過名稱顯式指定Vehicle 對象并調用它的drive() 函數。 但是如果直接執行`Vehicle.drive()`,函數調用中的this 會被綁定到Vehicle 對象而不是Car 對象,這并不是我們想要的。因此,我們會使用`.call(this)` 來確保drive() 在Car 對象的上下文中執行。 ~~~ 如果函數Car.drive() 的名稱標識符并沒有和Vehicle.drive() 重疊(或者說“屏蔽”)的話,就不需要實現方法多態, 因為調用mixin(..) 時會把函數Vehicle.drive() 的引用復制到Car 中,因此可以直接訪問this.drive()。 正是由于存在標識符重疊,所以必須使用更加復雜的顯式偽多態方法。 ~~~ 在支持相對多態的面向類的語言中,Car 和Vehicle 之間的聯系只在類定義的開頭被創建,從而只需要在這一個地方維護兩個類的聯系。 但是在JavaScript 中(由于屏蔽)使用顯式偽多態會在所有需要使用(偽)多態引用的地方創建一個函數關聯,這會極大地增加維護成本。此外,由于顯式偽多態可以模擬多重繼承,所以它會進一步增加代碼的復雜度和維護難度。 #### (2)混合復制 回顧一下之前提到的mixin(..) 函數: ~~~ // 非常簡單的mixin(..) 例子: function mixin( sourceObj, targetObj ) { for (var key in sourceObj) { // 只會在不存在的情況下復制 if (!(key in targetObj)) { targetObj[key] = sourceObj[key]; } } return targetObj; } ~~~ 分析一下mixin(..) 的工作原理。它會遍歷sourceObj(本例中是Vehicle)的屬性,如果在targetObj(本例中是Car)沒有這個屬性就會進行復制。由于我們是在目標對象初始化之后才進行復制,因此一定要小心不要覆蓋目標對象的原有屬性。 如果我們是先進行復制然后對Car 進行特殊化的話,就可以跳過存在性檢查。不過這種方法并不好用并且效率更低,所以不如第一種方法常用: ~~~ // 另一種混入函數,可能有重寫風險 function mixin( sourceObj, targetObj ) { for (var key in sourceObj) { targetObj[key] = sourceObj[key]; } return targetObj; } var Vehicle = { // ... }; // 首先創建一個空對象并把Vehicle 的內容復制進去 var Car = mixin( Vehicle, { } ); // 然后把新內容復制到Car 中 mixin( { wheels: 4, drive: function() { // ... } }, Car ); ~~~ JavaScript 中的函數無法(用標準、可靠的方法)真正地復制,所以你只能復制對共享函數對象的引用(函數就是對象)。如果你修改了共享的函數對象(比如ignition()),比如添加了一個屬性,那Vehicle 和Car 都會受到影響。 #### (3)寄生繼承 顯式混入模式的一種變體被稱為“**寄生繼承**”,它既是顯式的又是隱式的。 下面是它的工作原理: ~~~ // “傳統的JavaScript 類”Vehicle function Vehicle() { this.engines = 1; } Vehicle.prototype.ignition = function() { console.log( "Turning on my engine." ); }; Vehicle.prototype.drive = function() { this.ignition(); console.log( "Steering and moving forward!" ); }; // “寄生類” Car function Car() { // 首先,car 是一個Vehicle var car = new Vehicle(); // 接著我們對car 進行定制 car.wheels = 4; // 保存到Vehicle::drive() 的特殊引用 var vehDrive = car.drive; // 重寫Vehicle::drive() car.drive = function() { vehDrive.call( this ); console.log( "Rolling on all " + this.wheels + " wheels!" ); return car; } var myCar = new Car(); myCar.drive(); // 發動引擎。 // 手握方向盤! // 全速前進! ~~~ 首先我們復制一份Vehicle 父類(對象)的定義,然后混入子類(對象)的定義(如果需要的話保留到父類的特殊引用),然后用這個復合對象構建實例。 **2. 隱式混入** ~~~ var Something = { cool: function() { this.greeting = "Hello World"; this.count = this.count ? this.count + 1 : 1; } }; Something.cool(); Something.greeting; // "Hello World" Something.count; // 1 var Another = { cool: function() { // 隱式把Something 混入Another Something.cool.call( this ); } }; Another.cool(); Another.greeting; // "Hello World" Another.count; // 1 (count 不是共享狀態) ~~~ 通過在構造函數調用或者方法調用中使用`Something.cool.call( this )`,我們實際上“借用”了函數`Something.cool() `并在Another 的上下文中調用了它。最終的結果是Something.cool() 中的賦值操作都會應用在Another 對象上而不是Something 對象上。 雖然這類技術利用了this 的重新綁定功能,但是Something.cool.call( this ) 仍然無法變成相對(而且更靈活的)引用,所以使用時千萬要小心。通常來說,盡量避免使用這樣的結構,以保證代碼的整潔和可維護性。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看