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