js設計模式(一)-單例模式

寫在前面

(度過一陣的繁忙期,又能夠愉快的開始學習新知識了,一年來技術棧切來切去,卻總以爲js都還沒學完-_-)segmentfault

本文主要圍繞js的設計模式進行展開,對每一個設計模式從特徵,原理和實現方式幾個方面進行說明。因爲內容較長,因此拆分紅多篇文章。若是有不對的地方歡迎指出,閱讀前請注意幾點:設計模式

  1. 若是對js的類式繼承和閉包不太熟練的建議先閱讀相關內容,好比我前面寫過的js繼承(主要看到原型鏈繼承部分就好)js閉包,,
  2. 知識密度較大,建議邊思考,順便跑如下相關的代碼(若是碰到代碼有問題的歡迎指出),中途注意休息

正文

定義

也叫單體模式,核心思想是確保一個類只對應一個實例
雖然js是弱類型的語言,可是js也有構造函數和實例。因此這裏能夠理解爲確保屢次構造函數時,都返回同一個實例緩存

實現

根據定義,咱們須要實現一個構造函數,而且知足如下條件:閉包

function A(){
    //須要實現的函數內容
}
var a1 = new A() 
var b1 = new A()
a1 ==== b1 //true

在前面咱們說到了構造函數和實例,而且也知道了引用類型的值賦值的時候存放的實際是變量的地址指針,因此要實現這個構造函數的核心思路是:每次調用構造函數時,返回指向同一個對象的指針。 也就是說,咱們只在第一次調用構造函數時建立新對象,以後調用返回時返回該對象便可。因此重點變成了--如何緩存初次建立的變量對象。 函數

首先先排除全局變量,由於通常狀況下須要保證全局環境的純淨,其次全局變量容易被改寫,出現意外狀況。因此採用如下2種方案來實現緩存。學習

1. 使用構造函數的靜態屬性

由於構造函數自己也是對象,能夠擁有靜態屬性。因此能夠這樣實現:測試

function A(name){
    // 若是已存在對應的實例
   if(typeof A.instance === 'object'){
       return A.instance
   }
   //不然正常建立實例
   this.name = name
   
   // 緩存
   A.instance =this
   return this
}
var a1 = new A() 
var a2= new A()
console.log(a1 === a2)//true

這種方法的缺點在於靜態屬性是可以被人爲重寫的,不過不會像全局變量那樣被無心修改。this

2. 藉助閉包

經過閉包的方式來實現的核心思路是,當對象第一次被建立之後,重寫構造函數,在重寫後的構造函數裏面訪問私有變量。prototype

function A(name){
  var instance = this
  this.name = name
  //重寫構造函數
  A = function (){
      return instance
  }
}
var a1 = new A() 
var a2= new A()
console.log(a1 === a2)//true

到這裏咱們其實已經實現了最核心的步驟,可是這樣的實現存在問題,若是看過原型鏈繼承的小夥伴會注意到,若是咱們在第一次調用構造函數以後,因爲構造函數被重寫,那麼在以後添加屬性和方法到A的原型上,就會丟失。好比:設計

function A(name){
  var instance = this
  this.name = name
  //重寫構造函數
  A = function (){
      return instance
  }
}
A.prototype.pro1 = "from protptype1"

var a1 = new A() 
A.prototype.pro2 = "from protptype2"
var a2= new A()

console.log(a1.pro1)//from protptype1
console.log(a1.pro2)//underfined
console.log(a2.pro1)//from protptype1
console.log(a2.pro2)//underfined

重寫構造函數以後,,實際上原先的A指針對應的函數實際上還在內存中(由於instance變量還在被引用着,這裏的內容若是忘記了請看閉包),可是此時A指針已經指向了一個新的函數了,能夠簡單測試下:

console.log(a1.constructor ==== A)//false

因此接下來咱們應該解決這個問題,根據上文可知,咱們的重點是,調整原型實例之間的關係,因此應該這樣實現(這一塊忘記的仍是建議回頭看看js繼承裏面的那張函數、原型、實例之間的關係圖點擊直達):

function A(name){
  var instance = this
  this.name = name
 
  //重寫構造函數
  A = function (){
      return instance
  }
  
  // 第一種寫法,這裏實際上實現了一次原型鏈繼承,若是不想這樣實現,也能夠直接指向原來的原型
  A.prototype = this
  // 第二種寫法,直接指向舊的原型
  A.prototype = this.constructor.prototype
  
  instance = new A()
  
  // 調整構造函數指針,這裏實際上實現了一次原型鏈繼承,若是不想這樣實現,也能夠直接指向原來的原型
  instance.constructor = A
  
  return instance
}
A.prototype.pro1 = "from protptype1"

var a1 = new A() 
A.prototype.pro2 = "from protptype2"
var a2= new A()

console.log(a1.pro1)//from protptype1
console.log(a1.pro2)//from protptype2
console.log(a2.pro1)//from protptype1
console.log(a2.pro2)//from protptype2

如今一切就正常了。還有一種方式,是利用當即執行函數來保持私有變量,(當即執行函數的內容請看《詳解js中的函數部分》)原理也是閉包:

var A;
(function(name){
    var instance;
    A = function(name){
        if(instance){
            return instance
        }
        
        //賦值給私有變量
        instance = this
        
        //自身屬性
        this.name = name
    }
}());
A.prototype.pro1 = "from protptype1"

var a1 = new A('a1') 
A.prototype.pro2 = "from protptype2"
var a2 = new A('a2')

console.log(a1.name)
console.log(a1.pro1)//from protptype1
console.log(a1.pro2)//from protptype2
console.log(a2.pro1)//from protptype1
console.log(a2.pro2)//from protptype2

簡單說明一下上面的內容,首先利用在當即執行函數中保存一個私有變量instance,初次執行以後,第一次調用new A()以後,生成一個對象並讓instance指向該對象,從第二次開始,調用new A(),都只返回這個對象,

*特殊狀況

不少地方會提到,使用字面量直接建立一個對象也是一個單例模式的實例。這個說法我我的以爲並不夠嚴格,和同事探討以後以爲多是這樣(若是有有其餘看法的小夥伴歡迎指出):使用字面量寫法的時候,實際上至關於使用原生的Object函數new了一個對象,而後存儲到內存裏,以後咱們每次使用對應的指針去讀取時,讀到的都是這個對象。而我不認爲這是一個單例模式的緣由以下:

var obj1 = new Object({
  name:111
})

var obj2 = new Object({
  name:111
})

console.log(obj1===obj2)//false

我以爲既然兩次調用同一個構造函數,返回的不是同一個對象,那不就不能成爲單例模式。固然,這一部分是我我的的見解,讀者朋友仍是要注意區分。

小結

單例模式先說到這裏,後面會陸續補充其餘的設計模式。
感謝以前的熱心讀者,尤爲是爲我指出錯誤的小夥伴。而後依然是每次都同樣的結尾,若是內容有錯誤的地方歡迎指出;若是對你有幫助,歡迎點贊和收藏,轉載請徵得贊成後著明出處,若是有問題也歡迎私信交流,主頁添加了郵箱地址~溜了

相關文章
相關標籤/搜索