單例對象的類只能允許一個實例存在。
思路
- 有一個引用類對象
- 這個對象實例永遠只有一個
- 實現的基本步驟:
- 將構造函數定義爲私有函數,這樣其他處的代碼就無法通過調用該類的構造方法來實例化該類的對象,只有通過該類提供的靜態方法來得到該類的唯一實例
- 在該類內提供一個靜態方法,當我們調用這個方法時,如果類持有的引用不爲空就返回這個引用,如果類保持的引用爲空就創建該類的實例並將實例的引用賦予該類保持的引用。
單例模式的優點
系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以提高系統性能。
缺點
當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法
適用場合
- 需要平凡進行創建和銷燬的對象
- 創建對象時耗時過多或者耗費資源過多,但又經常用到的資源
- 工具類對象
- 頻繁訪問數據庫或者文件對象(緩存
實現單例模式
1.客戶端, 就是使用這類的使用者必須知道是一個單例的類,必須主動條用getInstance方法
2.並不能真正的阻止客戶端直接new Window
- es6 實現
class Window {
constructor (name) {
this.name = name;
}
static getInstance () {
if (!this.instance) {
this.instance = new Window()
}
return this.instance
}
}
let w1 = Window.getInstance()
let w2 = Window.getInstance()
console.log(w1 === w2)
- es5 實現
function Window (name) {
this.name = name
}
// 類上的方法,只能通過類來訪問
Window.getInstance = (function () {
let instance
return function () {
if (!instance) {
instance = new Window
}
return instance
}
})()
let w1 = Window.getInstance()
let w2 = Window.getInstance()
console.log(w1 === w2)
透明單利
let window = (function() {
let window;
let Window = function (name) {
if (window) {
return window
} else {
this.name = name
return (window = this)
}
}
return Window
})();
/*
* 1. 創建this = 空對象
* 2. new 關鍵字, 如果返回的是一個對象,那new 出來的對象就是我們返回的對象
* 3. 雖然這麼寫能夠實現單例,但是違反了函數單一原則的設計理念,需要進一步優化
*/
let w1 = Window.getInstance()
let w2 = Window.getInstance()
- 優化書寫方法
思路: 把類的實例的創建邏輯和單例邏輯分開
function Window (name) {
this.name = name
}
Window.prototype.getName = function () {
console.log(this.name)
}
let CreateSingle = (function () {
let instance;
return function (name) {
if (!instance) {
instance = new Window(name)
}
return instance
}
})()
let w1 = new CreateSingle('hello')
let w2 = new CreateSingle('hello')
console.log(w1 === w2)
- 進一步優化
上邊的寫法不能創建多個實例,這個進一步優化
function Window (name) {
this.name = name
}
function Dialog (name) {
this.name = name
}
Window.prototype.getName = function () {
console.log(this.name)
}
let CreateSingle = function (Constructor) {
let instance;
return function () {
if (!instance) {
Constructor.apply(this, arguments)
// 下邊 兩行代碼意思相同
// this.__proto = ConStructor.prototype
Object.setPrototypeOf(this, Constructor.prototype)
instance = this;
}
return instance
}
}
let CreateWindow = CreateSingle(Window)
let w1 = new CreateWindow('hello')
let w2 = new CreateWindow('leo')
console.log(w1 === w2)
let CreateDialog = CreateSingle(Dialog)
let d1 = new CreateDialog('hello')
let d2 = new CreateDialog('leo')
console.log(d1 === d2)
- 優化CreateSingle方法
function Window (name) {
this.name = name
}
function Dialog (name) {
this.name = name
}
Window.prototype.getName = function () {
console.log(this.name)
}
/*let CreateSingle = function (Constructor) {
let instance;
let SingleConstructor = function () {
if (!instance) {
Constructor.apply(this, arguments)
instance = this;
}
return instance
}
// 原形繼承
SingleConstructor.prototype = Object.create(Constructor.prototype)
return SingleConstructor
}*/
let CreateSingle = function (Constructor) {
let instance;
let SingleConstructor = function () {
if (!instance) {
instance = new Constructor(...arguments)
}
return instance
}
// 原形繼承
return SingleConstructor
}
let CreateWindow = CreateSingle(Window)
let w1 = new CreateWindow('hello')
let w2 = new CreateWindow('leo')
console.log(w1 === w2)
let CreateDialog = CreateSingle(Dialog)
let d1 = new CreateDialog('hello')
let d2 = new CreateDialog('leo')
console.log(d1 === d2)
單利模式應用
- 命名空間
- jQuery
- 複雜對象想的可讀性
// 示例
let utils = {}
utils.define = function (namespace, fn) {
namespace = namespace.split('.')
let fnName = namespace.pop()
let curr = utils
for (let i = 0; i < namespace.length; i++) {
let name = namespace[i]
if (!curr[name]) {
curr[name] = {} // 作爲容器
}
curr = curr[name]
}
curr[fnName] = fn
}
utils.define('req', function () {console.log('req')})
utils.define('requst.header', function () {console.log('utils.req.header')})
console.log(utils.req())
console.log(utils.requst.header())
適用場景
- redux, 數據共享
- 常用組件
<!DOCTYPE html>
<html>
<head>
<title>登錄框</title>
<style type="text/css">
.login{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
height: 200px;
background: #eee;
border: 1px solid #e4e4e4;
border-radius: 5px;
display: none;
text-align: center;
}
.show{
display: block;
}
</style>
</head>
<body>
<p><button class="show">顯示登錄框</button><button class="hide">隱藏登陸框</button></p>
</body>
<script type="text/javascript">
let show = document.querySelector('.show')
let hide = document.querySelector('.hide')
class Login {
constructor () {
this.el = document.createElement('div')
this.el.classList.add('login')
this.el.innerHTML = (`
<p><input type="input" name="name" placeholder="賬戶" /></p>
<p><input type="password" name="password" placeholder="密碼"></p>
<p><button>登錄</button></p>
`)
document.body.appendChild(this.el)
}
show () {
this.el.classList.add('show')
}
hide () {
this.el.classList.remove('show')
}
static getInstance () {
if (!this.instance) {
this.instance = new Login()
}
return this.instance
}
}
show.addEventListener('click', function () {
Login.getInstance().show()
})
hide.addEventListener('click', function () {
Login.getInstance().hide()
})
</script>
</html>
總結
始終記得一句話,凡是要知其然,知其所以然…