1. 程式人生 > 程式設計 >詳解js中的幾種常用設計模式

詳解js中的幾種常用設計模式

工廠模式

function createPerson(name,age){
   var o = new Object();   // 建立一個物件
   o.name = name;
   o.age = age;
   o.sayName = function(){
      console.log(this.name)
   }
   return o;   // 返回這個物件
}
var person1 = createPerson('ccc',18)
var person2 = createPerson('www',18)

工廠函式的問題:
工廠模式雖然解決了建立多個相似物件的問題,但是沒有解決物件識別問題(即怎樣知道一個物件的型別)。如下

person1 instanceof createPerson   // --> false
person1 instanceof Object   // --> true

建構函式模式

function Person(name,age){
 this.name = name;
 this.age = age;
 this.sayName = function(){
  console.log(this.name)
 }
}

var person1 = new Person('ccc',18)
var person2 = new Person('www',18)
person1.sayName()   // --> 'ccc'

person1 和person2 分別儲存著Person的一個不同的例項。這兩個物件都有一個constructor(建構函式)屬性指向Person。這正是建構函式模式勝過工廠模式的地方。如下:

console.log(person1 instanceof Person)   // --> true
console.log(person1 instanceof Object)   // --> true
console.log(person2 instanceof Person)   // --> true
console.log(person2 instanceof Object)   // --> true

建構函式模式與工廠模式的區別:

  1. 沒有顯式的建立物件
  2. 直接將屬性和方法賦給了this物件
  3. 沒有return 語句

要建立Person的新例項,必須使用new操作符。以這種方式呼叫建構函式實際上會經歷一下4個步驟:

  1. 建立一個新物件
  2. 將建構函式的作用域賦給新物件(因此this就指向了這個新物件)
  3. 執行建構函式中的程式碼(為這個新物件新增屬性)
  4. 返回新物件

建構函式的問題:

使用建構函式的重要問題,就是每個方法都要在每個例項上重新建立一遍。person1和person2中都有一個名為sayName()的方法,但那兩個方法不是同一個Function例項。因為在ECMAscript中函式就是物件,因此每定義一個函式,也就是例項化了一個物件。從邏輯角度上講,此時的建構函式也可以你這樣定義:

function Person(name,age){
 this.name = name;
 this.age = age;
 this.sayName = new Function('console.log(this.name)')   // eslint: The Function constructor is eval. (no-new-func)
}

這會導致,建立的不同的例項上的同名函式是不相等的,比如:console.log(person1.sayName() === person2.sayName()) // -->false,然而建立兩個完全相同的任務的Function例項是沒有必要的。可以通過把函式定義轉移到建構函式外部來解決這個問題。

function Person(name,age){
 this.name = name;
 this.age = age;
 this.sayName = sayName
}
function sayName(){
 console.log(this.name)
}
var person1 = new Person('ccc',18)

這樣,由於sayName包含的是一個指向函式的指標,因此person1和person2物件就共享了在全域性作用域中定義的同一個sayName()函式。這樣做確實解決了兩個函式做同一件事的問題,可是新問題又來了:在全域性作用域中定義的函式實際上只能被某個物件呼叫,這讓全域性作用域有點名不副實。
帶來的新問題:
如果物件需要定義很多方法,那麼就要定義很多個全域性函式,於是我們這個自定義的引用型別就絲毫沒有封裝性可言。

原型模式

關於原型,原型鏈內容不在此描述,只討論原型設計模式
我們建立的每一個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法。即不必在建構函式中定義物件例項的資訊,而是將這些資訊直接新增到原型物件中。

function Person(){
}
Person.prototype.name = 'ccc'
Person.prototype.age = 18
Person.prototype.sayName = function(){
 console.log(this.name)
}
var person1 = new Person()
person1.sayName()   // --> ccc

var person2 = new Person()
person2.sayName()   // --> ccc

console.log(person1.sayName === person2.sayName)   // --> true

原型模式的問題:

它省略了為建構函式傳遞引數初始化引數的環節,結果所有的例項在預設情況下都將取得相同的屬性值。另外,原型模式的最大問題是由其共享的本性所導致的。看如下問題:

function Person(){
}
Person.prototype = {
 constructor: Person,name: 'ccc',age: 18,friends:['www','aaa'],sayName: function () {
  console.log(this.name)
 }
}
var person1 = new Person()
var person2 = new Person()

person1.friends.push('bbb')

console.log(person1.friends)   // --> ["www","aaa","bbb"]
console.log(person2.friends)   // --> ["www","bbb"]
console.log(person1.friends === person2.friends)   // --> true

帶來的新問題:

如果我們的初衷就是這樣,所有的例項共用一個數組,那麼這個結果就是想要的。可是,例項一般都是要有屬於自己的全部屬性的,這個問題正是我們很少看到有人單獨使用原型模式的原因所在。

組合使用建構函式模式和原型模式

建立自定義型別的最常見方式,就是組合使用建構函式模式與原型模式。建構函式模式用於定義例項屬性,而原型模式用於定義方法和共享的屬性。這種方式還支援向建構函式傳遞引數。

function Person(name,age){
 this.name = name;
 this.age = age;
 this.friends = ['aaa','bbb']
}
Person.prototype = {
 constructor: Person,sayName: function(){
  console.log(this.name)
 }
}
var person1 = new Person('ccc',18)
person1.friends.push('ddd')

console.log(person1.friends)   // --> ["aaa","bbb","ddd"]
console.log(person2.friends)   // --> ["aaa","bbb"]
console.log(person1.friends === person2.friends)   // --> false
console.log(person1.sayName === person2.sayName)   // --> true

這種建構函式與原型混成的模式,是目前ECMAscript中使用最廣泛、認同度最高的一種建立自定義型別的方法。可以說,這是用來定義引用型別的一種預設方式。

動態原型模式

動態原型模式就是可以通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。

function Person(name,age){
 // 屬性
 this.name = name
 this.age = age
 // 方法
 if(typeof this.sayName !== 'function'){
  Person.prototype.sayName = function(){
   console.log(this.name)
  }
 }
}

var person1 = new Person('ccc',18)
person1.sayName()   // --> ccc

這裡只有在sayName()方法不存在的情況下,才會將它新增到原型中。這段程式碼只會在初次呼叫建構函式時才會執行。
注意:

  • 在這裡對原型所做的修改,能夠立即在所有例項中得到反映。
  • 使用動態原型模式時,不能使用物件字面量重寫原型。如果在已經建立了例項的情況下重寫原型,那麼就會切斷現有例項與新原型之間的聯絡。(參考原型與原型鏈中的內容)

其它模式

還有寄生建構函式模式和穩妥建構函式模式,可自行了解。以上所以知識內容來自《JavaScript高階程式設計》(第三版)。

以上就是詳解js中的幾種常用設計模式的詳細內容,更多關於JS 設計模式的資料請關注我們其它相關文章!