摘要:什么是單例模式單例模式是一種十分常用但卻相對而言比較簡單的單例模式。對象就是單例模式的體現(xiàn)。總結單例模式雖然簡單,但是在項目中的應用場景卻是相當多的,單例模式的核心是確保只有一個實例,并提供全局訪問。
1. 什么是單例模式?
單例模式是一種十分常用但卻相對而言比較簡單的單例模式。它是指在一個類只能有一個實例,即使多次實例化該類,也只返回第一次實例化后的實例對象。單例模式不僅能減少不必要的內存開銷, 并且在減少全局的函數(shù)和變量沖突也具有重要的意義。
1.1 最簡單的單例模式就算你對于單例模式的概念還比較模糊,但是我相信你肯定已經(jīng)使用過單例模式了。我們來看一下下面的一段代碼:
let timeTool = { name: "處理時間工具庫", getISODate: function() {}, getUTCDate: function() {} }
以對象字面量創(chuàng)建對象的方式在JS開發(fā)中很常見。上面的對象是一個處理時間的工具庫, 以對象字面量的方式來封裝了一些方法處理時間格式。全局只暴露了一個timeTool對象, 在需要使用時, 只需要采用timeTool.getISODate()調用即可。timeTool對象就是單例模式的體現(xiàn)。在JavaScript創(chuàng)建對象的方式十分靈活, 可以直接通過對象字面量的方式實例化一個對象, 而其他面向對象的語言必須使用類進行實例化。所以,這里的timeTool就已經(jīng)是一個實例, 且ES6中let和const不允許重復聲明的特性,確保了timeTool不能被重新覆蓋。
1.2 惰性單例采用對象字面量創(chuàng)建單例只能適用于簡單的應用場景,一旦該對象十分復雜,那么創(chuàng)建對象本身就需要一定的耗時,且該對象可能需要有一些私有變量和私有方法。此時使用對象字面創(chuàng)建單例就不再行得通了,我們還是需要采用構造函數(shù)的方式實例化對象。下面就是使用立即執(zhí)行函數(shù)和構造函數(shù)的方式改造上面的timeTool工具庫。
let timeTool = (function() { let _instance = null; function init() { //私有變量 let now = new Date(); //公用屬性和方法 this.name = "處理時間工具庫", this.getISODate = function() { return now.toISOString(); } this.getUTCDate = function() { return now.toUTCString(); } } return function() { if(!_instance) { _instance = new init(); } return _instance; } })()
上面的timeTool實際上是一個函數(shù),_instance作為實例對象最開始賦值為null,init函數(shù)是其構造函數(shù),用于實例化對象,立即執(zhí)行函數(shù)返回的是匿名函數(shù)用于判斷實例是否創(chuàng)建,只有當調用timeTool()時進行實例的實例化,這就是惰性單例的應用,不在js加載時就進行實例化創(chuàng)建, 而是在需要的時候再進行單例的創(chuàng)建。 如果再次調用, 那么返回的永遠是第一次實例化后的實例對象。
let instance1 = timeTool(); let instance2 = timeTool(); console.log(instance1 === instance2); //true2. 單例模式的應用場景 2.1 命名空間
一個項目常常不只一個程序員進行開發(fā)和維護, 然后一個程序員很難去弄清楚另一個程序員暴露在的項目中的全局變量和方法。如果將變量和方法都暴露在全局中, 變量沖突是在所難免的。就想下面的故事一樣:
//開發(fā)者A寫了一大段js代碼 function addNumber () {} //開發(fā)者B開始寫js代碼 var addNumber = ""; //A重新維護該js代碼 addNumber(); //Uncaught TypeError: addNumber is not a function
命名空間就是用來解決全局變量沖突的問題,我們完全可以只暴露一個對象名,將變量作為該對象的屬性,將方法作為該對象的方法,這樣就能大大減少全局變量的個數(shù)。
//開發(fā)者A寫了一大段js代碼 let devA = { addNumber() { } } //開發(fā)者B開始寫js代碼 let devB = { add: "" } //A重新維護該js代碼 devA.addNumber();
上面代碼中,devA和devB就是兩個命名空間,采用命名空間可以有效減少全局變量的數(shù)量,以此解決變量沖突的發(fā)生。
2.2 管理模塊上面說到的timeTool對象是一個只用來處理時間的工具庫,但是實際開發(fā)過程中的庫可能會有多種多樣的功能,例如處理ajax請求,操作dom或者處理事件。這個時候單例模式還可以用來管理代碼庫中的各個模塊,例如下面的代碼所示。
var devA = (function(){ //ajax模塊 var ajax = { get: function(api, obj) {console.log("ajax get調用")}, post: function(api, obj) {} } //dom模塊 var dom = { get: function() {}, create: function() {} } //event模塊 var event = { add: function() {}, remove: function() {} } return { ajax: ajax, dom: dom, event: event } })()
上面的代碼庫中有ajax,dom和event三個模塊,用同一個命名空間devA來管理。在進行相應操作的時候,只需要devA.ajax.get()進行調用即可。這樣可以讓庫的功能更加清晰。
3. ES6中的單例模式 3.1 ES6創(chuàng)建對象ES6中創(chuàng)建對象時引入了class和constructor用來創(chuàng)建對象。下面我們來使用ES6的語法實例化蘋果公司
class Apple { constructor(name, creator, products) { this.name = name; this.creator = creator; this.products = products; } } let appleCompany = new Apple("蘋果公司", "喬布斯", ["iPhone", "iMac", "iPad", "iPod"]); let copyApple = new Apple("蘋果公司", "阿輝", ["iPhone", "iMac", "iPad", "iPod"]);3.2 ES6中創(chuàng)建單例模式
蘋果這么偉大的公司明顯有且只有一個, 就是喬爺爺創(chuàng)建的那個, 哪能容別人進行復制?所以appleCompany應該是一個單例, 現(xiàn)在我們使用ES6的語法將constructor改寫為單例模式的構造器。
class SingletonApple { constructor(name, creator, products) { //首次使用構造器實例 if (!SingletonApple.instance) { this.name = name; this.creator = creator; this.products = products; //將this掛載到SingletonApple這個類的instance屬性上 SingletonApple.instance = this; } return SingletonApple.instance; } } let appleCompany = new SingletonApple("蘋果公司", "喬布斯", ["iPhone", "iMac", "iPad", "iPod"]); let copyApple = new SingletonApple("蘋果公司", "阿輝", ["iPhone", "iMac", "iPad", "iPod"]); console.log(appleCompany === copyApple); //true3.3 ES6的靜態(tài)方法優(yōu)化代碼
ES6中提供了為class提供了static關鍵字定義靜態(tài)方法, 我們可以將constructor中判斷是否實例化的邏輯放入一個靜態(tài)方法getInstance中,調用該靜態(tài)方法獲取實例, constructor中只包需含實例化所需的代碼,這樣能增強代碼的可讀性、結構更加優(yōu)化。
class SingletonApple { constructor(name, creator, products) { this.name = name; this.creator = creator; this.products = products; } //靜態(tài)方法 static getInstance(name, creator, products) { if(!this.instance) { this.instance = new SingletonApple(name, creator, products); } return this.instance; } } let appleCompany = SingletonApple.getInstance("蘋果公司", "喬布斯", ["iPhone", "iMac", "iPad", "iPod"]); let copyApple = SingletonApple.getInstance("蘋果公司", "阿輝", ["iPhone", "iMac", "iPad", "iPod"]) console.log(appleCompany === copyApple); //true4. 單例模式的項目實戰(zhàn)應用 4.1 實現(xiàn)登陸彈框
登陸彈框在項目中是一個比較經(jīng)典的單例模式,因為對于大部分網(wǎng)站不需要用戶必須登陸才能瀏覽,所以登陸操作的彈框可以在用戶點擊登陸按鈕后再進行創(chuàng)建。而且登陸框永遠只有一個,不會出現(xiàn)多個登陸彈框的情況,也就意味著再次點擊登陸按鈕后返回的永遠是一個登錄框的實例。
現(xiàn)在來梳理一下我登陸彈框的流程,在來進行代碼的實現(xiàn):
給頂部導航模塊的登陸按鈕注冊點擊事件
登陸按鈕點擊后JS動態(tài)創(chuàng)建遮罩層和登陸彈框
遮罩層和登陸彈框插入到頁面中
給登陸框中的關閉按鈕注冊事件, 用于關閉遮罩層和彈框
給登陸框中的輸入框添加校驗(此步驟略)
給登陸框中的確定按鈕添加事件,用于Ajax請求(此步驟略)
給登陸框中的清空按鈕添加事件,用于清空輸入框(此步驟略)
因為5,6是登陸框的實際項目邏輯, 和單例模式關系不大。下面的項目實戰(zhàn)代碼只實現(xiàn)1 - 4步,其余步驟讀者可自行進行擴展練習。完整的代碼可在 CodePen中進行查看。
4.1.1 給頁面添加頂部導航欄的HTML代碼4.1.2 使用ES6的語法創(chuàng)建Login類
class Login { //構造器 constructor() { this.init(); } //初始化方法 init() { //新建div let mask = document.createElement("div"); //添加樣式 mask.classList.add("mask-layer"); //添加模板字符串 mask.innerHTML = `4.1.3 給登陸按鈕添加注冊點擊事件`; //插入元素 document.body.insertBefore(mask, document.body.childNodes[0]); //注冊關閉登錄框事件 Login.addCloseLoginEvent(); } //靜態(tài)方法: 獲取元素 static getLoginDom(cls) { return document.querySelector(cls); } //靜態(tài)方法: 注冊關閉登錄框事件 static addCloseLoginEvent() { this.getLoginDom(".close-btn").addEventListener("click", () => { //給遮罩層添加style, 用于隱藏遮罩層 this.getLoginDom(".mask-layer").style = "display: none"; }) } //靜態(tài)方法: 獲取實例(單例) static getInstance() { if(!this.instance) { this.instance = new Login(); } else { //移除遮罩層style, 用于顯示遮罩層 this.getLoginDom(".mask-layer").removeAttribute("style"); } return this.instance; } }登錄框×用戶名:密碼:
//注冊點擊事件 Login.getLoginDom(".login-btn").addEventListener("click", () => { Login.getInstance(); })4.1.4 效果演示
完整的項目代碼見: CodePen(單例模式案例——登錄框)
上面的登陸框的實現(xiàn)中,我們只創(chuàng)建了一個Login的類, 但是卻實現(xiàn)了一個并不簡單的登陸功能。在第一次點擊登陸按鈕的時候,我們調用Login.getInstance()實例化了一個登陸框,且在之后的點擊中,并沒有重新創(chuàng)建新的登陸框,只是移除掉了"display: none"這個樣式來顯示登陸框,節(jié)省了內存開銷。
總結單例模式雖然簡單,但是在項目中的應用場景卻是相當多的,單例模式的核心是確保只有一個實例, 并提供全局訪問。就像我們只需要一個瀏覽器的window對象, jQuery的$對象而不再需要第二個。 由于JavaScript代碼書寫方式十分靈活, 這也導致了如果沒有嚴格的規(guī)范的情況下,大型的項目中JavaScript不利于多人協(xié)同開發(fā), 使用單例模式進行命名空間,管理模塊是一個很好的開發(fā)習慣,能夠有效的解決協(xié)同開發(fā)變量沖突的問題。靈活使用單例模式,也能夠減少不必要的內存開銷,提高用于體驗。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93564.html
摘要:設計模式的定義在面向對象軟件設計過程中針對特定問題的簡潔而優(yōu)雅的解決方案。從前由于使用的局限性,和做的應用相對簡單,不被重視,就更談不上設計模式的問題。 ‘從大處著眼,從小處著手’,以前對這句話一知半解,自從踏出校門走入社會,開始工作以來,有了越來越深的理解,偶有發(fā)現(xiàn)這句話用在程序開發(fā)中也有用,所以,近段時間開始嘗試著分析jQuery源碼,分析angularjs源碼,學習設計模式。 設...
摘要:簡單工廠模式簡單工廠模式又叫靜態(tài)工廠模式,由一個工廠對象決定創(chuàng)建某一種產(chǎn)品對象類的實例。工廠方法模式工廠方法模式的本意是將實際創(chuàng)建對象的工作推遲到子類中,這樣核心類就變成了抽象類。抽象工廠模式一般用在 1 什么是工廠模式? 工廠模式是用來創(chuàng)建對象的一種最常用的設計模式。我們不暴露創(chuàng)建對象的具體邏輯,而是將將邏輯封裝在一個函數(shù)中,那么這個函數(shù)就可以被視為一個工廠。工廠模式根據(jù)抽象程度的不...
摘要:簡單工廠模式簡單工廠模式又叫靜態(tài)工廠模式,由一個工廠對象決定創(chuàng)建某一種產(chǎn)品對象類的實例。工廠方法模式工廠方法模式的本意是將實際創(chuàng)建對象的工作推遲到子類中,這樣核心類就變成了抽象類。抽象工廠模式一般用在 1 什么是工廠模式? 工廠模式是用來創(chuàng)建對象的一種最常用的設計模式。我們不暴露創(chuàng)建對象的具體邏輯,而是將將邏輯封裝在一個函數(shù)中,那么這個函數(shù)就可以被視為一個工廠。工廠模式根據(jù)抽象程度的不...
閱讀 1887·2021-09-27 13:35
閱讀 3434·2019-08-30 14:16
閱讀 2489·2019-08-30 10:52
閱讀 869·2019-08-29 16:35
閱讀 1422·2019-08-29 15:22
閱讀 3649·2019-08-23 18:21
閱讀 3139·2019-08-23 18:00
閱讀 3128·2019-08-23 16:50