【JavaScript】 JS面向對象的模式與實踐

 

參考書籍編程

《JavaScript高級語言程序設計》—— Nicholas C.Zakas
《你不知道的JavaScript》  —— KYLE SIMPSON
 
在JS的面向對象編程中,咱們最爲關注的是兩種行爲,一是建立對象二是類繼承
 

JS建立對象

一.構造函數模式建立對象

第一種建立對象的方式是構造函數模式
 
以下所示, 將構造函數中的屬性和方法賦給一個新對象
/**
 * description: 構造函數模式建立對象
 */
function Type (p) {
  this.param = p; // 定義屬性
  this.method = function () { // 定義方法
    return this.param;
  }
}
          
var obj1 = new Type(1); // {param:1, method: function(){...}}
var obj2 = new Type(2); // {param:2, method: function(){...}}
console.log(obj1.method()) // 輸出1
console.log(obj2.method()) // 輸出2

 

 
當一個函數被加以new操做符前綴,它就會被當成一個構造函數調用並返回一個對象。因此構造函數和普通函數在形式上沒有差異,區別只是在於有沒有和new操做符搭配調用而已。
 
通常來講,咱們知道,一個方法沒有明確的調用對象時候,this的指向將是window, 但在構造函數建立對象時候, new操做符改變了this的指向, 使其和新建立的對象綁定。 因此構造函數體內執行的代碼至關於:
var obj =  new Object;
obj.param = p;
obj.method = function () { ... };

 

二.原型模式建立對象

構造函數的缺陷與加入原型的緣由

 
咱們知道, 原型(prototype)已經不知不覺地加入到JS面向對象的你們庭裏面來了, 但是他當初是如何被邀請進這個家庭裏面的呢? 實際上,這個「邀請人」正是構造函數。
 
利用構造函數建立對象看起來並沒有不妥,但它有個關鍵的問題: 冗餘的函數建立。
例如上文中咱們建立obj1和obj2的過程當中,method這個方法被重複建立了兩次
var obj1 = new Type(1); // {param:1, method: function(){...}}
var obj2 = new Type(2); // {param:2, method: function(){...}}

 

 
咱們發現, 和param屬性相反, method方法對不一樣的對象來講函數體是相同的,重複建立函數是一種對內存的浪費
this.method = function () { // 定義方法
  return this.param;
} 

 

因此你們就想: 將其變成一個能夠共享的方法或許更合適一些, 因而prototype就出如今JS面向對象的舞臺上了。加入原型的緣由,一大部分緣由就是爲了實現函數複用, 彌補構造函數的缺陷
 

構造函數和原型的關係

在JS的OO中,咱們能夠把對象屬性分爲兩部分:
 
  • 一部分是不一樣對象各自獨有的屬性, 例如上文中的param. 在需求上,咱們但願不一樣的對象擁有不一樣的param
  • 一部分是不一樣對象間共享的屬性, 例如上文中的method方法,在需求上,咱們但願不一樣的對象共同使用同一個method。
 
原型就是承載這一部分共享屬性的載體,它是一個對象,叫作prototype
 
同時這個prototype對象仍是在每一個函數(或構造函數)在一開始就帶有的屬性。
 
因此總體思路是: 對於共享的那部分屬性,從構造函數中「搬」到prototype對象中來:
 
因而咱們的代碼從:
function Type (p) {
  this.param = p; // 定義屬性
  this.method = function () { // 定義方法
    return this.param;
  }
}

 

變成了:
function Type (p) {
  this.param = p; // 不一樣對象各自獨有的屬性
}
Type.prototype.method = function () { // 不一樣對象共享的屬性
  return this.param;
}

 

這樣的話,咱們在利用構造函數建立對象的時候, 就不會作重複建立method方法這個多餘的行爲了,而是直接根據對象和構造函數的關係,直接從構造函數(Type.prototype)中的原型對象中取得method。
 
那麼問題來了, 怎麼在某個對象中取得method方法?
 

原型能是個普通對象嗎?(反向探究原型的工做機制)

若是你瞭解過JS,你固然知道原型有一系列的機制,但我如今先不講這些,而是從「prototype若是是個普通的對象會怎樣」, 反向探究原型建立對象須要哪些必要的工做機制
 
若是原型只是一個普通地不能再普通的對象的話——
 
1. 若是原型只是個普通對象,在被建立的對象中取得在原型中的屬性將會很麻煩
 
須要經過Object.getPrototypeOf取得原型對象的引用,而後才能從原型對象中取出method方法 。。。。 並且由於this綁定丟失的問題,方法還得用call調用!
 
function Type (p) {
  this.param = p; // 不一樣對象各自獨有的屬性
}
 
Type.prototype.method = function () { // 不一樣對象共享的屬性
  return this.param;
}
          
// 臥槽,臥槽! 要是這麼麻煩我乾脆別用原型了
var obj = new Type(1);
// 超級麻煩!
console.log(Object.getPrototypeOf(obj).method.call(obj)) // 輸出1
// 也很麻煩!同時這種簡化破環了咱們面向對象的初衷
console.log(Type.prototype.method.call(obj)) //輸出1    

 

 
反向思考: 原型的機制應該使得對象中能用很簡單的方式使用原型中的屬性, 最好是:,對象obj能直接經過obj.method訪問其構造函數的原型對象中的屬性, 這樣的話,不管屬性在構造函數的this中,仍是在prototype對象,建立的對象使用該屬性的方式都是相同的! 這樣,具體的實現就變成了和使用無關的「黑盒」 。
 
 
2. 若是原型只是個普通對象, 你將不得不考慮prototype對象中數據的維護問題 由於在這種假設下,prototype對象是全部對象的單一數據源, 因此對象A若是重寫了該方法,對象B使用的就不是一開始prototype對象中定義的方法, 而是對象A修改事後的方法,而全部其餘對象也和對象B同樣。 這樣的話,咱們就不能在某個對象中重寫原型中的方法了。
 
反向思考: 原型的機制應當確保prototype對象中屬性的穩定性, 換句話說,就是在某個對象重寫原型中具備的屬性時候, 僅僅起到覆蓋原型中的同名屬性做用就能夠了, 而不要不要修改原型中的屬性!
 
因此,JavaScript中的「原型」固然不是一個普通的對象,它是prototype對象以及背後的一系列機制造成的一個「總體」!
 
其實在上面「反向思考」的描述裏,我已經說明了原型的工做機制了,不過下面仍是更具體地說明一下:
 

原型的工做機制

1. 在用new 操做符建立對象實例的時候, 該對象將得到一個指向其構造函數的原型對象的內部指針,所以,咱們能夠經過「.」運算符直接訪問原型中的屬性, 就和訪問實例屬性同樣(前提是實例對象中沒有同名屬性)
 
function Type (p) {
  this.param = p; // 不一樣對象各自獨有的屬性
}
Type.prototype.method = function () { // 不一樣對象共享的屬性
  return this.param;
}         
var obj = new Type(1);
console.log(obj); 
obj.method();

 

 
 
2. 當訪問對象中的某個屬性時,先在對象實例中查找該屬性, 若是在實例中找到了該屬性名,則返回該屬性的值,若是沒有找到,則繼續在對應構造函數的原型對象中尋找, 若是找到則返回原型中的屬性值
function Type () {
  this.a = '實例對象中的a';
}
Type.prototype.a = 'prototype中的a';
Type.prototype.b = 'prototype中的b';
var obj = new Type();
console.log(obj);   //輸出 : {a: "實例對象中的a"}
console.log(obj.a); //輸出:實例對象中的a
console.log(obj.b); //輸出: prototype中的b

 

 
如代碼所示, 由於對於屬性a, 由於在實例對象中查找到了, 因此就直接返回實例中的屬性值,而對於屬性b,z在實例對象中沒有查找到,因此返回原型中的屬性值
 
 

 

 
上面咱們說的是在實例中查找屬性時,原型的做用機制,那麼在添加屬性的過程當中會發生什麼事情呢?
 
3.在爲實例添加屬性時, 只會覆蓋其構造函數的原型對象中的同名屬性,這種狀況叫作「屬性屏蔽」,也就是之後經過obj.屬性名,就會讀取到實例中的屬性了,而不是原型中的屬性, 可是! 原型對象中的屬性是不會被修改的!不管這個屬性是基本類型仍是引用類型。這就是原型對象屬性的「穩定性」
 
例如:
function Type () {}
Type.prototype.str = '字符串'
Type.prototype.num = 1;
Type.prototype.arr = [1,2,3];
var obj = new Type();
console.log(obj); // {}
obj.str = '覆蓋後字符串';
obj.num = 2;
obj.arr = [3,4,5];
console.log(obj.str); // 覆蓋後字符串
console.log(obj.num); // 2
console.log(obj.arr); // [3.4.5]
          
console.log(Type.prototype.str); // 字符串
console.log(Type.prototype.num); // 1
console.log(Type.prototype.arr); // [1, 2, 3]

 

 
直接添加基本類型的值(str, num),以及引用類型的值到實例中,是不會修改原型中的同名屬性的,只會屏蔽
 
 

 

 
可是你們還須要考慮另外一種狀況:若是咱們不添加實例屬性,而是直接修改屬性,這時會發生什麼呢?例如在obj仍是空對象,沒有任何屬性的時候,咱們就嘗試經過obj.num去增長num的值(此時只有原型對象中有num),原型對象中的num還會被修改嗎?而在這個空的實例對象中又會發生什麼事情呢?
讓咱們來看看——
 
4. 在實例對象中沒有該屬性時,直接修改其在原型中的屬性值, 若是這個屬性是基本類型,那麼會在原型的屬性值的基礎上爲實例對象添加修改後的屬性值, 而原型對象中的屬性值是不變的
 
function Type () {}
Type.prototype.str = '字符串'
Type.prototype.num = 1;
var obj = new Type();
console.log(obj); // 空實例對象 {}
obj.str +=',加點東西'; // 嘗試直接修改屬性
obj.num += 1;       // 嘗試直接修改屬性
          
console.log(obj.str); // 字符串,加點東西
console.log(obj.num); // 2
          
console.log(Type.prototype.str); // 字符串 
console.log(Type.prototype.num); // 1  
 
console.log(obj); // {str: "字符串,加點東西", num: 2}

 

 
在這裏咱們作了一個看起來「荒誕」的操做: 在實例對象中尚未添加屬性值的時候,就嘗試修改屬性值(基本類型)。並且修改完後,咱們發現obj已經從控對象{ }變成了 {str: "字符串,加點東西", num: 2}, 修改屬性變成了一種「變相」的添加屬性
 
  1. 在實例中沒有該屬性時,直接修改基本類型的實例屬性等同於爲其添加屬性,並且添加的屬性值是在原型對象屬性值的基礎上進行的
  2. 在直接修改基本類型的實例屬性時, 原型對象中的屬性仍然沒有變化! 這進一步證實了原型對象中的數據具備必定的「穩定性」

 

 

 
也許你能猜到接下來的內容是什麼了。。。由於我在<點4>中強調了兩點:「直接修改的值是基本類型」和原型對象具備'必定'的穩定性。
沒錯, 若是被直接修改的屬性是屬性是對象或者數組這類引用類型的數據的話, 原型對象中的數據也變得「不穩定」了
 
 
5.在實例對象中沒有該屬性時,直接修改其在原型中的屬性值, 若是這個屬性是引用類型的,那麼! 原型中的屬性值會被修改!也就是說,在這種狀況下。 原型中的屬性值是「不穩定」的! 可能會被某個對象直接篡改。
 
function Type () {}
Type.prototype.objProperty =  {a: 1};
Type.prototype.arrProperty = [1,2];
var obj = new Type();
console.log(obj.objProperty) // {a: 1}
console.log(obj.arrProperty) // [1, 2]
          
obj.objProperty.a = 111;  // 直接修改引用類型的屬性值
obj.arrProperty.push(3);  // 直接修改引用類型的屬性值
          
// 原型對象中的屬性值被修改了
console.log(Type.prototype.objProperty) // {a: 111}
console.log(Type.prototype.arrProperty) // [1, 2, 3]
 
console.log(obj); // 輸出 {} obj仍是空的!!

 

 
能夠看到, 在實例中尚無該屬性,就直接修改屬性值時, 對於基本類型和引用類型的屬性, 狀況是截然相反的。不只沒有在實例對象obj中生成屬性, 還直接地把原型對象中的屬性篡改了。 這種「不穩定」並非咱們想要的。
 
因此,通常來講,咱們不能把數組或純對象的數據放到原型中
 
(不能這樣作:)

 

 
總結下上面說的1~5點:
 
在原型機制下:——
 
1. 在獲取原型對象的屬性值時,其方式能夠和獲取實例屬性的方式相同。(嗯嗯,挺好的~~)
2. 在查找屬性值時,實例對象中沒有該屬性則能夠由原型對象提供屬性默認值,若是實例對象有該屬性,則屏蔽原型中的屬性(嗯嗯,挺好的~)
3. 爲實例屬性添加屬性值時, 不管該值是基本類型仍是引用類型, 都不會修改原型對象中的同名屬性,而是僅起到屏蔽做用(嗯嗯,挺好的~)
4. 直接修改某屬性值(在實例對象中沒有,而在原型中有),若是這個屬性是基本類型(字符串,數字,布爾型),那麼也不會修改原型中的屬性,同時在實例對象中會生成修改後的屬性值(嗯嗯,挺好的~)
5. 直接修改某屬性值(在實例對象中沒有,而在原型中有),若是這個屬性是引用類型(對象,數組),那麼原型中屬性會被實例對象直接篡改!同時實例對象並無添加該屬性!(這是什麼鬼!!!)
 
 

原型在OO體系中暴露的缺陷

 
大凡瞭解JS的同窗,大概也都知道原型是一個矛盾綜合體: 強大卻也脆弱, 讓人高興卻也讓人煩惱而第5點揭示的就是這種矛盾的集中爆發點
 
1到4點都告訴咱們:"哎呀protoType在JS面向對象裏是多好的一個東西啊! 你看建立實例對象後, 它會提供默認的屬性值,並且還賦予實例重寫屬性這種充分的自由,並且最重要的是——原型看起來很穩定嘛!"     嗯,看起來
 
但是在第5點中,咱們看到,在這種狀況下,protoType中的數據一點也不穩定, 這種不穩定性甚至直接讓人質疑它是否有理由存在在面向對象的世界中
 
例如緊接上面第5點中的代碼
以下:
 
function Type () {}
Type.prototype.arrProperty = [1,2];
var obj1 = new Type();
var obj2 = new Type();
          
console.log(obj2.arrProperty) // [1, 2]
          
obj1.arrProperty.push(3); 
          
console.log(obj2.arrProperty); // [1, 2, 3] 我怎麼被修改了???

 

 
能夠看到, 因爲引用了prototype對象中的arrProperty, obj2.arrProperty一開始是[1,2],但因爲另外一個對象obj1直接修改了obj1.arrProperty,而很不巧的是, arrProperty是引用類型, 因此像上面第5點描述的那樣,原型對象中的arrProperty屬性被篡改了, 因此obj2.arrProperty也被改爲了[1, 2, 3]
 
此時的obj2可謂是一臉"黑人問號臉":爲何我會無緣無故地被修改了?? 並且修改個人竟然仍是和我分庭抗禮的obj1,這是什麼道理呀?

 

 
 
 
在類和對象的概念中, 咱們通常會把類想象成一個靜態的模板,並且類纔是擁有"控制權"的
 
在原型模式下, prototype對象就是這個模板, 但咱們發現,在上述的某個實例對象直接修改原型的屬性值時, prototype這個"類"模板竟然會被動態地修改, 並且修改它的是某個對象, 這讓它徹底喪失了"控制權",而落到了下面的全部對象當中。這是在面向對象中沒法讓人接受的
 
因此,通常來講,咱們不能把數組或純對象的數據放到原型中
 

對原型模式的評價

評價:原型模式是不完善的OO模式, 因此總體上看,它沒法獨立地完成面向對象設計的各類工做,而須要和構造函數模式配合使用
 
從做用上看: 原型是對構造函數模式的一種輔助和補充, 或者說某些不足的優化(解決冗餘的函數建立的問題)
從地位上看: 構造函數仍然是OO的核心,而原型則在其次
 
 
正因如此, 咱們大多數時候將原型模式和構造函數模式配合使用來建立對象,這一模式被稱爲——
 

三.組合模式建立對象

若是咱們將方法和共享的屬性放入原型中,而將須要實例化的屬性放入構造函數的函數體中,咱們就能夠既能實現函數複用,同時又能經過構造函數向實例屬性傳遞參數,能夠說是集二者之長,而避二者之短, 這就是組合模式
 
以下所示
function Person (name, age) {
  this.name = name;
  this.age = age;
  this.friends = ['Wang','Li'];
}
       
Person.prototype.sayName = function () {
  return this.name;
}
       
var person1 = new Person('Zhang',13);
var person2 = new Person('Huang',15);
       
person1.friends.push('Peng')
console.log(person1.friends); // ["Wang", "Li", "Peng"]
console.log(person2.friends); // ["Wang", "Li"]
       
console.log(person1.sayName()) // Zhang
console.log(person2.sayName()) // Huang
       
console.log(person1.sayName == person2.sayName) // true

 

上面的代碼中,由於name, age,friends是實例屬性,不一樣的對象是各不相同的,因此咱們把它們放在構造函數中,而sayName方法應該是由不一樣的對象共享的,因此咱們把它放在prototype對象中。
 
從person1.friends和person2.friends能夠看出,建立對象時,實例屬性被拷貝了多份不一樣的副本,它們互不影響,而由person1.sayName == person2.sayName爲true可知, 方法調用的是同一個, 這種效果正是咱們想的。
 
哪些屬性應該(或能夠)被放入原型中?
 
1. 函數: 應該儘可能被放入原型中
雖然是它引用類型,但在JS裏咱們通常是沒有辦法在原來的基礎上修改函數的(像array.push())那樣,而只能爲函數從新賦值,因此不用擔憂會修改原型中的函數屬性
 
2. 不變的共享屬性: 能夠被放入原型中
若是一個屬性是共享的, 並且肯定它在未來也必定不會被修改,那它也能夠放入原型中
 
3. 可能被修改的基本類型的屬性:放入原型中也不會有問題,但最好不要這樣作
沒有必要,而且這種寫法難以讓人理解
 

JS中的繼承

繼承是JS面向對象設計的另外一大重點, 你會發現,下面我關於繼承的分析,和上文建立對象的分析思路是徹底相同的
 

一.借用構造函數實現繼承

 
在JS中,要讓一個子類繼承父類, 須要在子類的構造函數中經過call方法調用父類的構造函數,同時傳入this做爲第一個參數,表示將
函數執行的做用域綁定到當前做用域, 以下:
 
/**
 * description: 借用構造函數實現繼承
 */
function superType () { // "父類"構造函數
  this.name = "aaa";
  this.sayName = function () {
    return this.name
  }
}
       
function subType () { // "子類"構造函數
  superType.call(this); // 調用「父類「的構造函數
}
       
var obj = new subType();
console.log(obj.name);  // 輸出 aaa
console.log(obj.sayName());  // 輸出 aaa

 

 
 
向父類的構造函數中寫入參數的繼承
 
若是咱們想要向父類的構造函數寫入參數的話,可使用call方法的第二個參數:
 
function superType (name) { // "父類"構造函數
  this.name = name;
  this.sayName = function () {
    return this.name
  }
}
       
function subType (name) { // "子類"構造函數
  superType.call(this,name); // 調用「父類「的構造函數,並傳遞參數
}
       
var obj = new subType("XXX")
console.log(obj.name);   // 輸出XXX
console.log(obj.sayName()); // 輸出XXX

 

 
 
可是,一樣的問題又出現了, 若是多個子類繼承同一個父類, 可能會出現父類構造函數的方法被重複拷貝屢次的狀況。
 
因而,爲了減小這種冗餘的函數拷貝的問題,以節約內存, 原型又特麼登場了,此次它帶上了它的大舅媽——「原型鏈」
 

二.利用原型和原型鏈實現繼承

 
以下所示:
/**
 * description: 利用原型和原型鏈實現繼承
 */
function superType () { // "父類"構造函數
  this.name = 'XXX'
}
superType.prototype.sayName = function () {
  return this.name;
}
       
function subType () {  } // "子類"構造函數
       
// 建立父類構造函數的實例,並賦給子類的原型
subType.prototype = new superType();
       
var obj = new subType();
console.log(obj.sayName()); // 輸出 XXX

 

                                                                   
最關鍵的一句代碼是:
 
subType.prototype = new superType(); // {name: "XXX"}

 

它具備兩個方面的做用:
1. 建立superType的實例{name: "XXX"}, 並做爲subType構造函數的原型對象
2. 建立原型鏈
 
回顧以前說的:「被建立的實例都有一個指向其構造函數的原型對象的內部指針」,因此結合
subType.prototype = new superType();// #1
var obj = new subType(); // #2

 

可知, superType實例有一個指向superType.prototype的內部指針,而且經過#1將其賦給了subType.prototype,因此subType.prototype就有了一個指向superType.prototype指針了
 
而經過#2處的代碼使得obj也有了一個指向subType.prototype的內部指針
 
綜上: 如今內部指針的指向是: obj -> subType.prototype -> superType.prototype
 
這一條內部指針指向造成的連接, 就叫作原型鏈
 
 

原型鏈下屬性的搜索機制

 
在原型的工做機制下,搜索屬性的步驟是:
 
1> 在實例對象中搜索
2> 在實例對象的構造函數的原型中搜索
 
而在原型的基礎上構造了原型鏈後, 這個步驟變成了:
 
1> 在實例對象中搜索 (obj)
2> 在當前的構造函數的原型中搜索 (subType.prototype)
3> 在當前的構造函數的"父類"構造函數的原型中搜索(superType.prototype)
 
在上述代碼中obj.sayName()的調用過程是這樣的:
1. 在obj中搜索有無sayName方法——無,向下搜索
2. 在subType.prototype中搜索有無sayName方法—— 無,向下搜索
3. 在superType.prototype中搜索有無sayName方法—— 找到了!,調用該方法返回name屬性
(繼續。。)
4. 在obj中搜索有無name屬性—— 無,向下搜索
5. 在subType.prototype中搜索有無name屬性—— 找到了! 返回name的值「XXX」
 

僅僅使用原型鏈實現繼承的缺點

 
僅使用原型鏈實現繼承的缺點,和原型模式建立對象的缺點同樣:
 
1. 你沒法向父類構造函數中傳遞參數
2. 依然存在對象篡改原型對象屬性,進而影響全部其餘對象的問題
 
function superType () { // "父類"構造函數
  this.arr = [1,2]
}
       
function subType () {  } // "子類"構造函數
subType.prototype = new superType();
       
var obj1 = new subType();
var obj2 = new subType();
console.log(obj2.arr); // 輸出 [1, 2]
       
obj1.arr.push(3);
       
console.log(obj2.arr); // 輸出 [1, 2, 3] 臥槽,我又被亂改了!

 

三.組合繼承

 
故名思義, 組合繼承就是指將原型鏈繼承和借用構造函數繼承組合在一塊兒,從而發揮二者之長的一種繼承模式
 
具體的思路是: 用原型鏈實現對方法和共享屬性的繼承; 而經過借用構造函數實現對實例屬性的繼承。
 
這樣,既能實現函數複用, 同時又能保證每一個屬性有它本身的屬性
 
/**
 * description: 組合繼承的例子
 */
function SuperType (name) {
  this.name = name;
  this.colors = ['red','blue','green'];
}
       
SuperType.prototype.sayName = function () {
  console.log(this.name)
}
       
function SubType(name, age) {
  SuperType.call(this,name); // 繼承實例屬性
  this.age = age;
}
       
SubType.prototype = new SuperType();  // 繼承方法
SubType.prototype.sayAge = function () { // 寫入新的方法
  console.log(this.age)
}
       
var obj1 = new SubType('Wang', 20);
obj1.colors.push('black');
console.log(obj1.colors); // ["red", "blue", "green", "black"]
obj1.sayName(); // Wang
obj1.sayAge();  // 20
        
var obj2 = new SubType('Zhang', 23);
console.log(obj2.colors); // ["red", "blue", "green"]
obj2.sayName(); // Zhang
obj2.sayAge(); // 23

 

 
要注意的是, 經過SuperType.call(this,name); 拷貝到SubType中的屬性,是會覆蓋SubType.prototype中的屬性的(屬性屏蔽) 因此這個時候SubType.prototype = new SuperType();的主要做用是取得對原型鏈中SuperType.prototype的引用
 

面向對象中的原型——OO體系和OLOO體系的碰撞和融合

 
饒了一圈咱們仍是講一講原型吧, 從JS建立對象和繼承這兩大任務上看, 你已經大致瞭解到了原型在OO中的重要性, 但卻總覺原型的各類原理和表現難以讓人理解, 就好像一個外來人進入了一個羣體裏, 儘管他儘可能中規中矩,但看起來卻總和周圍人事格格不入。
 
這很正常,由於這個外來人原本就不在這個羣體(OO)裏面, 而是來自另一個羣體(OLOO)。
 
實際上,咱們能夠把面向對象看做一種設計模式(OO), 而把原型所體現的設計模式歸結爲另一種設計模式(OLOO)。
 

OO設計模式

 
OO,也即面向對象, 在它的世界裏, 有一個父類和一堆子類, 父類是定義通用的行爲, 而子類在父類的基礎上定義更爲細化的行爲。
通常來講,但願子類的結構和父類大致相同,並鼓勵子類經過重寫實現多態
 
舉個例子:
咱們最多見的交通工具和車子的比喻, 交通工具類Vehicle是一個通用的父類,而car,bus等子類是在父類的基礎上細化出來的子類
 
如下爲僞代碼
 
class Vehicle {
  setColor (color) { this.color = color }
  setWheels (num) { this.wheels = num }
  setEngine (num) { this.engine = num }
}
 
class Car extends Vehicle { // 繼承
  setWheels () { this.wheels = 4 } // 方法重寫
  setEngine (1) { this.engine = 1 } // 方法重寫
}

 如圖設計模式

 

 
 

OLOO設計模式

 
全稱是Object linked to Other Object,即對象關聯設計模式, 在它的設計裏面, 由一個主對象責提供其餘衍生對象所須要調用的方法或屬性,其餘衍生對象須要某些基礎的方法,就 到這個主對象中來「拿」,這個拿的過程,就是方法的委託
 
例如:
 
如下爲僞代碼
// 工具對象
VehicleParts = {
  setWheels: function (num) { ... } // 安裝車輪
  setEngine:  function (num) { ... } // 安裝引擎
}
 
// 衍生對象
Car.protoType = VehicleParts; // 委託
Car.build = function () {
  setWheels(4); // 4輪子
  setEngine(1); // 1引擎
}
 
Bike.protoType = VehicleParts; // 委託
Bike.build = function () {
  setWheels(2); // 2輪子
  setEngine(0); // 0引擎
}

 如圖數組

 

 

 
而原型,就是一個來自於OLOO的世界而進入OO的世界的人物
 

對原型恰當的認知方式

原型一直以來難以讓人理解的緣由是, 咱們不遺餘力想要把它歸入JS面向對象設計的一部分,可是又不得不面對它在使用中諸多不知足面向對象要求的表現, 例如我上面提到的建立對象和繼承中它存在的問題。
 
也許最恰當的認知方式就是: 不要把它看做面向對象體系的一部分。
 
它是OO體系和OLOO體系的碰撞,也是OLOO體系對於OO的補充,但並它不是OO體系的延伸,歷來也不是 
 
 
相關文章
相關標籤/搜索