導(dǎo)航
[React 從零實(shí)踐01-后臺(tái)] 代碼分割
[React 從零實(shí)踐02-后臺(tái)] 權(quán)限控制
[React 從零實(shí)踐03-后臺(tái)] 自定義hooks
[React 從零實(shí)踐04-后臺(tái)] docker-compose 部署react+egg+nginx+mysql
[React 從零實(shí)踐05-后臺(tái)] Gitlab-CI使用Docker自動(dòng)化部署
[源碼-webpack01-前置知識(shí)] AST抽象語(yǔ)法樹(shù)
[源碼-webpack02-前置知識(shí)] Tapable
[源碼-webpack03] 手寫(xiě)webpack - compiler簡(jiǎn)單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex
[源碼-vue01] data響應(yīng)式 和 初始化渲染
[源碼-vue02] computed 響應(yīng)式 - 初始化罗标,訪(fǎng)問(wèn)庸队,更新過(guò)程
[源碼-vue03] watch 偵聽(tīng)屬性 - 初始化和更新
[源碼-vue04] Vue.set 和 vm.$set
[源碼-vue05] Vue.extend
[源碼-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue項(xiàng)目
[部署03] gitlab-CI
[數(shù)據(jù)結(jié)構(gòu)和算法01] 二分查找和排序
[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉(zhuǎn)換 和 運(yùn)算符
[深入07] 瀏覽器緩存機(jī)制(http緩存機(jī)制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫(xiě)Promise
[深入20] 手寫(xiě)函數(shù)
[深入21] 數(shù)據(jù)結(jié)構(gòu)和算法 - 二分查找和排序
[深入22] js和v8垃圾回收機(jī)制
[深入23] JS設(shè)計(jì)模式 - 代理,策略闯割,單例
(一) 前置知識(shí)
- 設(shè)計(jì)模式一直都沒(méi)敢寫(xiě)彻消,主要是個(gè)人覺(jué)得理解不難,主要膽怯在運(yùn)用于生產(chǎn)項(xiàng)目
(1) 一些單詞
pattern: 模式 // design pattern: 設(shè)計(jì)模式
strategy: 策略宙拉,戰(zhàn)略
performance: 性能宾尚,績(jī)效
salary: 工資,薪水
bonus: 獎(jiǎng)金
singleton: 單身谢澈,單例模式
(2) Storage
- 概念
- 兩個(gè)對(duì)象部署了 ( Storage接口 )煌贴,分別是 ( window.localStorage ) 和 ( window.sesstionStorage )
- 方法
-
Storage.setItem(key, value)
key 必須是符串,非字符串會(huì)被轉(zhuǎn)成字符串
value 也必須是字符串澳化,非字符串會(huì)被轉(zhuǎn)成字符串
如果key已經(jīng)存在崔步,則新的value覆蓋舊的value
如果存儲(chǔ)空間已滿(mǎn),該方法會(huì)拋錯(cuò)
Storage.setItem('name', 'woow_wu7') 等價(jià)于 Storage.name = 'woow_wu7'
-
Storage.getItem(key)
- key不存在缎谷,則返回 null
-
Storage.removeItem(key)
- 清除key對(duì)應(yīng)的value
-
Storage.clear()
- 清除Storage中的所有數(shù)據(jù)
- 返回值是undefined
-
Storage.key(number)
- 接受一個(gè)整數(shù)作為參數(shù),返回該位置對(duì)應(yīng)的鍵值
-
Storage.setItem(key, value)
-
事件
window.addEventListener('storage', onStorageChange)
- 該監(jiān)聽(tīng)函數(shù)的event對(duì)象
- StorageEvent.key: 發(fā)生變動(dòng)的鍵名
- StorageEvent.newValue: 新的鍵值,字符串
- Storage.oleValue: 舊的鍵值列林,字符串
- Storage.storageArea: 返回整個(gè)對(duì)象
- Storage.url: 表示原始觸發(fā) storage 事件的 ( 網(wǎng)頁(yè)地址 )
-
特別注意
該事件不在導(dǎo)致數(shù)據(jù)變化的當(dāng)前頁(yè)面觸發(fā)瑞你,而是在 ( 同一個(gè)域名 ) 的其他窗口觸發(fā)
- 所以:如果瀏覽器只有一個(gè)窗口,可能看不到該事件觸發(fā)
- 所以:可以實(shí)現(xiàn)多窗口通信
image
(3) 對(duì)象的命令空間
var namespace = {
a: () => { console.log('a') },
b: () => { console.log('b') },
}
(二) JS設(shè)計(jì)模式
(1) 代理模式
(1) 代理模式的定義
- 代理模式:
指給某一個(gè)對(duì)象提供一個(gè) ( 代理對(duì)象 )希痴,并由 ( 代理對(duì)象 ) 來(lái)控制 ( 原對(duì)象的引用 )者甲,代理模式類(lèi)似 ( 中介 )
(2) 為什么要使用代理模式?
- 中介隔離
- ( 客戶(hù)對(duì)象 ) === ( 代理對(duì)象 ) === ( 委托對(duì)象 )
- 一些情況下砌创,客戶(hù)對(duì)象不想或者不能直接引用一個(gè)委托對(duì)象虏缸,則代理對(duì)象可以充當(dāng)中介作用
- 開(kāi)閉原則,增加功能
- ( 代理類(lèi) ) 除了是 ( 客戶(hù)類(lèi) ) 和 ( 委托類(lèi) ) 的中介之外嫩实,還可以給代理類(lèi)增加額外的功能來(lái)擴(kuò)展委托類(lèi)的功能刽辙,這樣我們就可以直接修改代理類(lèi)而不是直接修改委托類(lèi),符合代碼設(shè)計(jì)的開(kāi)閉原則
- ( 代理類(lèi) ) 主要負(fù)責(zé)委托類(lèi)的 ( 預(yù)處理消息甲献, 過(guò)濾消息宰缤, 轉(zhuǎn)發(fā)消息給委托類(lèi),對(duì)返回結(jié)果的處理 )
- ( 代理類(lèi) ) 本身不提供服務(wù)晃洒,而是通過(guò)調(diào)用委托類(lèi)相關(guān)的方法慨灭,來(lái)提供特定的服務(wù),真正的業(yè)務(wù)功能還是由委托類(lèi)來(lái)實(shí)現(xiàn)
(3) 實(shí)戰(zhàn)
(3-1) 代理ajax請(qǐng)求 - 增加緩存功能
- 作用
- 通過(guò) 代理函數(shù)proxyRequest() 調(diào)用 cache() 使得 request() 方法做了一層緩存
- 緩存每次請(qǐng)求的參數(shù)球及,如果參數(shù)一樣氧骤,就直接返回之前參數(shù)對(duì)應(yīng)的 Map 中的緩存的 value
- 原理
利用 ( 代理模式 ) 給原由的函數(shù)添加新的邏輯但又不影響原函數(shù),相當(dāng)于用 ( 函數(shù)組合 ) 來(lái)實(shí)現(xiàn) ( 復(fù)用邏輯和添加邏輯 )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// Map
// 1.實(shí)例屬性 mapInstance.size
// 2.原型方法 mapInstance.get() set() has() delete() clear()
const mapInstance = new Map()
// request 發(fā)送請(qǐng)求吃引,返回一個(gè)promise實(shí)例
const request = (params) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(`data: ${params}`)
}, 2000)
})
}
// cache 緩存 ( 請(qǐng)求參數(shù) )和 ( 請(qǐng)求結(jié)果 promise )
const cache = (params) => {
if (mapInstance.has(params)) { // Map.prototype.has
return mapInstance.get(params) // Map.prototype.get
}
mapInstance.set(params, request(params))
return request(params)
}
// proxyRequest 代理請(qǐng)求
const proxyRequest = (params) => {
return cache(params)
}
// 當(dāng)參數(shù)一樣時(shí)语淘,是從Map中返回的緩存值
proxyRequest('11').then(data1 => {
proxyRequest('11').then(data2 => console.log('data1 === data2 ? 這里返回true ', data1 === data2))
})
</script>
</body>
</html>
(3-2) 緩存代理 - 處理緩存過(guò)期時(shí)間 - class版本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
class Storage {
constructor({ type, expires }) {
this.type = type // 類(lèi)型
this.expires = expires // 過(guò)期時(shí)間
this.storageType = {
local: 'localStorage',
session: 'sessionStorage'
}
}
// 取
// 代理原生 getItem() 方法,根據(jù)緩存過(guò)期時(shí)間际歼,判斷數(shù)據(jù)是否過(guò)期
getItem(key) {
const now = +new Date()
const { value, setTimes } = JSON.parse(window[this.storageType[this.type]].getItem(key))
if (now > setTimes + this.expires) {
window[this.storageType[this.type]].removeItem(key) // 如果過(guò)期惶翻,清除該 Storage
return null
}
return value
}
// 存
// 代理原生的 setItem() 方法,添加緩存時(shí)間
setItem(key, value) {
window[this.storageType[this.type]].setItem(key, JSON.stringify({
value,
setTimes: +new Date()
}))
}
}
const localStorage = new Storage({type: 'local', expires: 1000})
localStorage.setItem('name', 'woow_wu7')
console.log('未過(guò)期', localStorage.getItem('name'))
setTimeout(() => {
console.log('過(guò)期', localStorage.getItem('name'))
}, 2000)
</script>
</body>
</html>
2021/05/03 優(yōu)化
(3-2) 優(yōu)化緩存代理 - 處理緩存過(guò)期時(shí)間 - class版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
class Storage {
constructor(type, expires) {
this.storage = window[type];
this.expires = expires;
}
// 存
// 代理原生的 setItem() 方法鹅心,添加緩存過(guò)期時(shí)間
setItem = (key, value) => {
this.storage.setItem(
key,
JSON.stringify({
value,
setTime: +new Date(), // --------------------- ( 存 ) 的時(shí)間戳
})
);
};
// 取
// 代理原生的 getItem() 方法吕粗,根據(jù)傳入的過(guò)期時(shí)間,來(lái)判斷數(shù)據(jù)是否過(guò)期
getItem = (key) => {
const now = +new Date(); // --------------------- ( 取 ) 的時(shí)間戳
const { value, setTime } = JSON.parse(this.storage.getItem(key));
if (now - setTime > this.expires) {
this.storage.removeItem(key); // -------------- 過(guò)期刪除
}
return this.storage.getItem(key);
};
}
const localStorageInstance = new Storage("localStorage", 5000);
localStorageInstance.setItem("name", "woow_wu7");
console.log("未過(guò)期", localStorageInstance.getItem("name"));
setTimeout(() => {
console.log("過(guò)期", localStorageInstance.getItem("name"));
}, 6000);
</script>
</body>
</html>
(3-3) 緩存代理 - 處理緩存過(guò)期時(shí)間 - function版本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Storage(type, expires) {
this.type = type
this.expires = expires
this.storageType = {
local: 'localStorage',
sesstion: 'sesstionStorage'
}
}
Storage.prototype.getItem = function(key) {
const now = +new Date()
const { value, setTimes } = JSON.parse(window[this.storageType[this.type]].getItem(key))
if (now - setTimes > this.expires) {
window[this.storageType[this.type]].removeItem(key)
return null
}
return value
}
Storage.prototype.setItem = function(key, value) {
window[this.storageType[this.type]].setItem(key, JSON.stringify({
value,
setTimes: +new Date()
}))
}
const storage = new Storage('local', 1000)
storage.setItem('name', 'woow_wu7')
console.log('未過(guò)期', storage.getItem('name'))
setTimeout(() => {
console.log('過(guò)期', storage.getItem('name'))
}, 2000)
</script>
</body>
</html>
(2) 策略模式
(1) 概念
- 定義一系列算法旭愧,把它們一個(gè)個(gè)封裝起來(lái)颅筋,并使它們可以 ( 相互替換 )
- 將 ( 不變的部分 ) 和 ( 變化的部分 ) 隔開(kāi)
- (
策略模式
) 的主要目的就是將 (算法的使用
) 和 (算法的實(shí)現(xiàn)
) 分離開(kāi)來(lái)
(2) 組成 - 策略模式組要包含 ( 策略類(lèi) ) 和 ( 環(huán)境類(lèi) )
-
策略類(lèi)
- 封裝了具體的算法,并負(fù)責(zé)具體的計(jì)算過(guò)程
-
環(huán)境類(lèi)
context- 環(huán)境類(lèi)context接受客戶(hù)的請(qǐng)求输枯,隨后把請(qǐng)求 ( 委托 ) 給某一個(gè)策略類(lèi)
環(huán)境類(lèi)context中要維持對(duì)某個(gè)策略類(lèi)的引用
(3) 特點(diǎn)
-
避免多重選擇語(yǔ)句出現(xiàn):策略模式利用 (
組合议泵,委托,多態(tài)
) 等技術(shù)和思想桃熄,可以有效的避免 (多重條件選擇語(yǔ)句if...else
) 的情況 - 符合開(kāi)放封閉原則:將算法封裝在獨(dú)立strategy策略中先口,使得它們?nèi)菀浊袚Q,理解,擴(kuò)展
- 算法可復(fù)用:可以復(fù)用在系統(tǒng)其他地方碉京,從而避免冗余重復(fù)的工作
(4) 缺點(diǎn)
- 必須了解所有策略:必須了解各個(gè)策略的不同點(diǎn)厢汹,才能實(shí)現(xiàn)一個(gè)特定的策略
(5) 實(shí)戰(zhàn)
(5-1) 計(jì)算獎(jiǎng)金
- S績(jī)效 - 4倍工資
- A績(jī)效 - 3倍工資
- B績(jī)效 - 2倍工資
bonus: 獎(jiǎng)金
salary: 工資
performance: 績(jī)效,性能
strategy: 策略
strategy pattern 策略模式
(1) 未經(jīng)過(guò)任何優(yōu)化的寫(xiě)法
- 缺點(diǎn)
- 具有很多if...else語(yǔ)句
- 缺乏擴(kuò)展性:如要添加C績(jī)效谐宙,則需要修改內(nèi)部的函數(shù)實(shí)現(xiàn)烫葬,違反開(kāi)放封閉原則
- 復(fù)用性差
function getBonus(performance, salary) { // bonus獎(jiǎng)金 performance績(jī)效 salary獎(jiǎng)金
if ( performance === 'S') return 4 * salary;
if ( performance === 'A') return 3 * salary;
if ( performance === 'B') return 2 * salary;
}
getBonus('S', 1000) // 輸出:4000
---------------
(2) 使用 - 函數(shù)組合 - 重構(gòu)代碼
function getS (salary) { return 4 * salary }
function getA (salary) { return 3 * salary }
function getB (salary) { return 2 * salary }
function getBonus(performance, salary) {
if ( performance === 'S') return getS(salary)
if ( performance === 'A') return getA(salary)
if ( performance === 'B') return getB(salary)
}
getBonus('S', 1000) // 輸出:4000
---------------
(3) 使用 - 策略模式strategyPattern - 重構(gòu)代碼
- 優(yōu)點(diǎn)
- 消除了大量if...else語(yǔ)句
- 所有計(jì)算獎(jiǎng)金bonus的邏輯都不在getBonus函數(shù)即環(huán)境類(lèi)context中,而是分布在各個(gè)策略對(duì)象strategy中
- context環(huán)境類(lèi)不負(fù)責(zé)計(jì)算凡蜻,只是負(fù)責(zé)將請(qǐng)求委托給strategy策略類(lèi)
const strategy = {
S: (salary) => 4 * salary,
A: (salary) => 3 * salary,
B: (salary) => 2 * salary,
}
const getBonus = (performance, salary) => strategy[performance](salary)
getBonus('S', 1000) // 輸出:4000
(5-2) 表單驗(yàn)證
- 未優(yōu)化的代碼如下
(1) 未優(yōu)化的代碼如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="javascript:void(0)" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button>表單提交</button>
</form>
<script>
const form = document.querySelector('form')
form.onsubmit = () => {
// form.username可以訪(fǎng)問(wèn)到 name="username" 的 input元素節(jié)點(diǎn)
if (form.username.value === '') {
console.log('用戶(hù)名不能為空')
return false
}
if (form.password.value.length < 6) {
console.log('密碼長(zhǎng)度不能少于6位')
return false
}
}
</script>
</body>
</html>
-
使用策略模式 - 優(yōu)化表達(dá)驗(yàn)證
- 第一步:將驗(yàn)證邏輯封裝到 ( 策略對(duì)象strategy中 )
- 第二步:將用戶(hù)的請(qǐng)求通過(guò) ( 環(huán)境對(duì)象context ) 委托給策略對(duì)象strategy來(lái)處理
(2) 使用策略模式 - 優(yōu)化表達(dá)驗(yàn)證
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="javascript:void(0)" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button>表單提交</button>
</form>
<script>
// 策略對(duì)象 strategy
// 注意:
// 1. 這里每個(gè)方法可以能有 ( 兩個(gè) ) 或者 ( 三個(gè)參數(shù) )
// 2. 很巧妙的利用了
// - "isEmpty".split(';') => ["isEmpth"].shift() => [].unshift(value) => [value].push(err) => [value, err]
// - "minLength:6".split(";") => ["minLength", "6"].shift() => ['6'].unshift(value) => [value, '6'].push(err) => [value, '6', err]
// - 最后就是兩種情況: [value, err] 或 [value, minLength, err]
// ------------------------------------ strategys
const strategys = {
notEmpty: (value, err) => { if (value === '') return err },
minLength: (value, minLength, err) => { if (value.length < minLength) return err },
}
// 環(huán)境類(lèi) context
// ------------------------------------ Validator
class Validator {
constructor() {
this.rules = [] // 規(guī)則數(shù)組搭综,用于存放所有的規(guī)則函數(shù)
}
add = (dom, rule, err) => {
const ruleToArr = rule.split(':') // ['isEmpty'] 或 ['minLength', 6]
this.rules.push(() => { // 每次沒(méi)push一個(gè)匿名函數(shù)到this.rules中
const strategy = ruleToArr.shift() // 'isEmpty' 或 'minLength'
ruleToArr.unshift(dom.value) // 頭部添加
ruleToArr.push(err) // 尾部添加
return strategys[strategy].apply(null, ruleToArr) // ruleToArr = [value, err] 或 [value, minLength, err]
})
}
start = () => {
for(let i = 0; i < this.rules.length; i++) { // 遍歷執(zhí)行每一個(gè)函數(shù)
let err = this.rules[i]();
if (err) { return err }
}
}
}
// ------------------------------------ form元素節(jié)點(diǎn)
const form = document.querySelector('form')
form.onsubmit = (e) => {
e.preventDefault()
const validator = new Validator()
validator.add(form.username, 'notEmpty', '用戶(hù)名不能為空') // add
validator.add(form.password, 'minLength:6', '密碼不能少于6位') // add
const err = validator.start() // start
if (err) {
console.log('err', err)
return false
}
}
</script>
</body>
</html>
(3) 單例模式 singleton
(1) 定義
- 保證一個(gè)類(lèi)只有一個(gè)實(shí)例,并提供一個(gè)訪(fǎng)問(wèn)它的全局訪(fǎng)問(wèn)點(diǎn)
- 優(yōu)點(diǎn)
- ( 創(chuàng)建對(duì)象 ) 和 ( 管理單例 ) 被分布在不同的方法中
(2) 應(yīng)用場(chǎng)景
- 全局變量
- 連接數(shù)據(jù)庫(kù)划栓,防止多次連接或斷開(kāi)
- 全局狀態(tài)管理redux vuex
- 登陸框兑巾,form表單,loading層等
(3) 實(shí)現(xiàn)
(3-1) 閉包實(shí)現(xiàn)一個(gè)單例
- 缺點(diǎn):需要調(diào)用 getInstance 函數(shù)創(chuàng)建對(duì)象
function Singleton(name) { // 構(gòu)造函數(shù), singleton: 單身, 注意:箭頭函數(shù)不能作為構(gòu)造函數(shù)茅姜,arguments闪朱,yeild命令
this.name = name
}
Singleton.getInstance = (() => {
let instance = null // 閉包變量
return (name) => {
if (!instance) {
instance = new Singleton(name)
}
return instance
}
})(); // IIFE立即調(diào)用的函數(shù)表達(dá)式,注意小括號(hào)和中括號(hào)開(kāi)頭的前一條語(yǔ)句需要加分號(hào)钻洒,或者小括號(hào)中括號(hào)前加分號(hào)
const a = Singleton.getInstance('A')
const b = Singleton.getInstance('B') // 這一次傳參沒(méi)用了
console.log('a === b', a === b) // true
(3-2) 函數(shù)實(shí)現(xiàn)一個(gè)單例
- 缺點(diǎn):需要調(diào)用 getInstance 函數(shù)創(chuàng)建對(duì)象
function Singleton(name) {
this.name = name
}
Singleton.getInstance = function(name) {
// 注意:
// 1. 這里的 this 指向的是 Singleton 奋姿,而不是 Singleton生成的實(shí)例
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
const a = Singleton.getInstance('A')
const b = Singleton.getInstance('B')
console.log('a === b', a === b)
console.log('a', a)
console.log('b', b)
console.log('Singleton.instance', Singleton.instance) // instance直接是Singleton的屬性
(3-3) 透明的單例模式
- 優(yōu)點(diǎn):解決12中的需要調(diào)用 createInstance 函數(shù)來(lái)生成對(duì)象的缺點(diǎn),這里直接通過(guò)new調(diào)用
- 缺點(diǎn):不符合單一職責(zé)原則素标,這個(gè)對(duì)象其實(shí)負(fù)責(zé)了兩個(gè)功能:?jiǎn)卫蛣?chuàng)建對(duì)象
var Singleton = (function() {
var instance = null
return function(name) {
if (instance) return instance
this.name = name
return instance = this // 返回實(shí)例對(duì)象称诗,instance僅僅是為了做判斷
}
})(); // IIFE返回一個(gè)構(gòu)造函數(shù)
const a = new Singleton('A')
const b = new Singleton('B')
console.log('a === b', a === b)
console.log('a', a)
(3-4) es6實(shí)現(xiàn)單例
class Book {
constructor(name) {
if (!Book.intance) {
this.name = name
Book.intance = this
}
return Book.intance
}
}
const english = new Book('english')
const chinese = new Book('chinese')
console.log('english === english', english === english)
console.log('english', english)
console.log('chinese', chinese)
(4) 實(shí)戰(zhàn) - 登陸框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="login">登陸</button>
<script>
function init() {
const button = document.getElementById('login')
button.addEventListener('click', showModal, false)
}
init() // 綁定監(jiān)聽(tīng)
var singleton = (() => {
var instance = null
return function(fn) {
if (!instance) {
instance = fn.call(null)
}
return instance
}
})() // IIFE單例子模式
function createModal() {
const div = document.createElement('div')
div.innerHTML = '登陸框'
div.style.setProperty('width', '300px')
div.style.setProperty('height', '300px')
div.style.setProperty('border', '1px solid black')
document.body.appendChild(div)
return div
}
function showModal() {
singleton(createModal)
}
</script>
</body>
</html>