本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些本身的思考。但願對你們有所幫助。html
js設計模式--單例模式es6
js設計模式--策略模式segmentfault
js設計模式--代理模式設計模式
單例模式的定義是:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。緩存
單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比線程池、全局緩存、瀏 覽器中的 window 對象等。閉包
在 JavaScript 開發中,單例模式的用途一樣很是普遍。試想一下,當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸浮窗,而這個登陸浮窗是惟一的,不管單擊多少 次登陸按鈕,這個浮窗都只會被建立一次,那麼這個登陸浮窗就適合用單例模式來建立。app
優勢:建立對象和管理單例的職責被分佈在兩個不一樣的方法中dom
var instance = null var getInstance = function(arg) { if (!instance) { instance = arg } return instance } var a = getInstance('a') var b = getInstance('b') console.log(a===b)
這種定義一個全局變量的方式很是不優雅,也很差複用代碼函數
var Singleton = function( name ){ this.name = name; }; Singleton.getInstance = (function(){ var instance = null; return function( name ){ if ( !instance ){ instance = new Singleton( name ); } return instance; } })(); var a = Singleton.getInstance('a') var b = Singleton.getInstance('b') console.log(a===b)
有些同窗可能對閉包不大理解,下面用函數實現一下this
function Singleton(name) { this.name = name this.instance = null } Singleton.getInstance = function(name) { if (!this.instance) { this.instance = new Singleton(name) } return this.instance } var a = Singleton.getInstance('a') var b = Singleton.getInstance('b') console.log(a===b)
2,3這兩種方式也有缺點,就是咱們必須調用getInstance來建立對象,通常咱們建立對象都是利用new操做符
var Singleton = (function() { var instance Singleton = function(name) { if (instance) return instance this.name = name return instance = this } return Singleton })() var a = new Singleton('a') var b = new Singleton('b') console.log(a===b)
這中方法也有點缺點:不符合單一職責原則,這個對象其實負責了兩個功能:單例和建立對象
下面咱們分離這兩個職責
var People = function(name) { this.name = name } var Singleton = (function() { var instance Singleton = function(name) { if (instance) return instance return instance = new People(name) } return Singleton })() var a = new Singleton('a') var b = new Singleton('b') console.log(a===b)
這中方法也有點缺點:代碼不能複用。若是咱們有另一個對象也要利用單例模式,那咱們不得不寫重複的代碼
var People = function(name) { this.name = name } var Singleton = function(Obj) { var instance Singleton = function() { if (instance) return instance return instance = new Obj(arguments) } return Singleton } var peopleSingleton = Singleton(People) var a = new peopleSingleton('a') var b = new peopleSingleton('b') console.log(a===b)
到這裏已經比較完美了,等等這只是es5的寫法,下面咱們用es6來實現一下
class People { constructor(name) { if (typeof People.instance === 'object') { return People.instance; } People.instance = this; this.name = name return this; } } var a = new People('a') var b = new People('b') console.log(a===b)
咱們都知道:JavaScript 實際上是一門無類(class-free)語言,,生搬單例模式的概念並沒有意義。
單例模式的核心是確保只有一個實例,並提供全局訪問。
咱們能夠用一下幾種方式來另類實現
好比var a = {},這時全局就只有一個a對象
但全局變量存在不少問題,它很容易形成命名空間污染,咱們用如下兩種方式解決
var namespace1 = { a: function () { alert(1); }, b: function () { alert(2); } };
另外咱們還能夠動態建立命名空間
var MyApp = {}; MyApp.namespace = function (name) { var parts = name.split('.'); var current = MyApp; for (var i in parts) { if (!current[parts[i]]) { current[parts[i]] = {}; } current = current[parts[i]]; } }; MyApp.namespace('event'); MyApp.namespace('dom.style'); console.dir(MyApp); // 上述代碼等價於: var MyApp = { event: {}, dom: { style: {} } };
var user = (function () { var __name = 'sven', __age = 29; return { getUserInfo: function () { return __name + '-' + __age; } } })();
下面咱們來實現一個點擊登陸按鈕彈出登陸框的例子
<html> <body> <button id="loginBtn">登陸</button> </body> <script> var loginLayer = (function () { var div = document.createElement('div'); div.innerHTML = '我是登陸浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; })(); document.getElementById('loginBtn').onclick = function () { loginLayer.style.display = 'block'; }; </script> </html>
上面這種方式若是用戶沒有點擊登陸按鈕,也會在一開始就建立登陸框
<html> <body> <button id="loginBtn">登陸</button> </body> <script> var createLoginLayer = function () { var div = document.createElement('div'); div.innerHTML = '我是登陸浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; }; document.getElementById('loginBtn').onclick = function () { var loginLayer = createLoginLayer(); loginLayer.style.display = 'block'; }; </script> </html>
這種方式每次點擊按鈕都會建立一個登陸框
var createLoginLayer = (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('loginBtn').onclick = function () { var loginLayer = createLoginLayer(); loginLayer.style.display = 'block'; };
這種方式不夠通用,不符合單一職責原則
var getSingle = function (fn) { var result; return function () { return result || (result = fn.apply(this, arguments)); } }; var createLoginLayer = function () { var div = document.createElement('div'); div.innerHTML = '我是登陸浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; }; var createSingleLoginLayer = getSingle(createLoginLayer); document.getElementById('loginBtn').onclick = function () { var loginLayer = createSingleLoginLayer(); loginLayer.style.display = 'block'; }; //下面咱們再試試建立惟一的iframe 用於動態加載第三方頁面: var createSingleIframe = getSingle(function () { var iframe = document.createElement('iframe'); document.body.appendChild(iframe); return iframe; }); document.getElementById('loginBtn').onclick = function () { var loginLayer = createSingleIframe(); loginLayer.src = 'http://baidu.com'; };
至此已經完美