javascript單例模式的理解


javascript單例模式的理解

閱讀目錄

理解單例模式

單例模式的含義是: 保證一個類只有一個實例,並提供一個訪問它的全局訪問點。實現的方法是:使用一個變量來標志當前是否已經為某個類創建過對象,如果創建了,則在下一次獲取該類的實例時,直接返回之前創建的對象,否則就創建一個對象。這就確保了一個類只有一個實例對象。

比如如下代碼是一個簡單的單例模式代碼實例:

var Singleton = function(name){
    this.name = name;
    // 使用instance 該標志來判斷是否創建了一個實例
    this.instance = null; 
};
Singleton.prototype.getName = function(){
    console.log(this.name);
};
Singleton.getInstance = function(name) {
    if(!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
}

現在我們可以來使用下,初始化下,如下代碼:

var a = Singleton.getInstance("aa");
var b = Singleton.getInstance("bbb");
console.log(a);
console.log(b);

打印如下:

繼續如下測試:

console.log(a === b);  // true

a.getName();  // aa

b.getName();  // aa

a.test = "test";

console.log(b.test); // test

如上代碼測試,可以看到,先是實例化一次,傳aa給name參數,保存到a變量中,第二次再次調用getIstance方法,由於實例已經存在,所以使用之前第一次創建過的對象,因此 a ===b 為true,a.getName()和b.getName()值打印都為aa;

我們還可以像如下方式來編寫代碼:

var Singleton = function(name){
    this.name = name; 
};
Singleton.prototype.getName = function(){
    console.log(this.name);
};
Singleton.getInstance = (function(){
    var instance = null;
    return function(name){
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();

使用代理實現單例模式

比如我現在想在頁面上創建一個div元素,如下使用代理實現單例模式的代碼:

var CreateDiv = function(html) {
    this.html = html;
    this.init();
};
CreateDiv.prototype.init = function(){
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
};
var ProxySingletonCreateDiv = (function(){
    var instance;
    return function(html) {
        if(!instance) {
            instance = new CreateDiv(html);
        }
        return instance;
    }
})();
var a = new ProxySingletonCreateDiv("aa");
var b = new ProxySingletonCreateDiv("bbb");
console.log(a === b); // true

如上代碼:我們把負責管理單例的邏輯移到了ProxySingletonCreateDiv 函數中,CreateDiv函數就是一個普通的函數,就是只是負責創建div的方法,那么具體的管理單例的邏輯交給ProxySingletonCreateDiv函數;

理解惰性單例

惰性單例的含義是:在需要的時候才創建對象實例,而前面我們講的是頁面加載完的時候就創建實例;比如我們在頁面上一個彈出窗口的div,還有許多其他的顯示元素,如果有些用戶不點擊那個彈窗的話,那么在頁面初始化的時候多創建了一些dom節點,如果我們使用惰性單例的話,我們就可以在用戶需要的時候才去創建dom節點;

我們首先來看看在頁面加載完成的時候去創建div彈窗。這個彈窗一開始是隱藏的,當用戶點擊某個按鈕的時候,這個彈窗才顯示;代碼如下:

<button id="btn">請點擊我</button>

var CreateDiv = (function(){
    var div = document.createElement('div');
    div.innerHTML = "我是彈窗測試";
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
})();
document.getElementById("btn").onclick = function(){
    CreateDiv.style.display = "block";    
};
惰性代碼如下所示:
var CreateDiv = function(){
    var div = document.createElement('div');
    div.innerHTML = "我是彈窗測試";
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
};
document.getElementById("btn").onclick = function(){
    var createDiv = CreateDiv();
    createDiv.style.display = "block";    
};

如上代碼,我們點擊按鈕的時候,才去創建div元素,但是每次點擊的時候,我們都得創建元素,這樣也不合理的。但是如上代碼,我們可以使用一個變量來判斷是否已經創建過div彈窗;如下所示:

var CreateDiv = (function(){
    var div;
    return function(){
        if(!div) {
            div = document.createElement('div');
            div.innerHTML = "我是彈窗測試";
            div.style.display = "none";
            document.body.appendChild(div);
        }
        return div;
    }
})();
document.getElementById("btn").onclick = function(){
    var createDiv = CreateDiv();
    createDiv.style.display = "block";    
};

編寫通用的惰性單例

如上代碼雖然完成了惰性單例,但是有些問題;

  1. 違反了單一職責原則;比如創建對象和管理單例的邏輯都放在CreateDiv對象內部;
  2. 沒有把代碼抽象出來,比如上面的是創建一個div元素,但是以后我想創建一個script元素或者一個iframe元素的話,那么我們還需要復制上面的代碼重寫下;

比如如果我現在按照上面創建div的方法,現在我們需要再創建一個iframe元素的話,代碼需要改成如下:

var createIframe = (function(){
    var iframe;
    return function(){
        if(!iframe) {
            iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            document.body.appendChild(iframe);
        }
        return iframe;
    }
})();

我們現在肯定在考慮如何把上面的代碼公用出來,這樣就可以實現抽象的代碼,管理單例的邏輯代碼其實可以抽象出來,這個邏輯是一樣的,使用一個變量來標志是否創建過對象,如果是,在下次直接返回這個已經創建好的對象;

我們可以把這些邏輯封裝在getSingle函數內部,創建對象的方法fn被當成參數動態傳入getSingle函數;如下代碼:

var getSingle = function(fn){
    var result;
    return function(){
        return result || (fn.apply(this,arguments));
    };
};

下面我們是使用getSingle創建一個div的方法如下:

var CreateDiv = function(){
    var div = document.createElement('div');
    div.innerHTML = "我是彈窗測試";
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
};
// 創建單例
var createSingleDiv = getSingle(CreateDiv); 

document.getElementById("btn").onclick = function(){
    // 調用單例方法
    var createDiv = createSingleDiv();
    createDiv.style.display = "block";    
};

比如現在我們需要創建一個iframe,那么代碼如下:

var createSingleIframe = getSingle(function(){
    var iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    return iframe;
});
document.getElementById("btn").onclick = function(){
    // 調用單例方法
    var createSingleIframe = createSingleIframe();
    createSingleIframe.src = "http://cnblogs.com";    
};

單例模式使用場景

有一些對象我們只需要一個的情況下,比如彈窗這樣的,全局緩存,游覽器window對象等。

單例模式只會創建一個實例,且僅有一個實例,比如我們一剛開始講到的,

var a = Singleton.getInstance("aa");

var b = Singleton.getInstance("bbb");

console.log(a === b);  // true

a.getName();  // aa

b.getName();  // aa

我們明明第一次傳的是aa,第二次傳的參數是bbb,為什么都調用getName()方法后都打印出aa呢,這就是單例模式只創建一個實例的地方;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM