本文基于《JavaScript设计模式与开发实践》一书,用一些例子总结一下JS常见的设计你模式与实现方法。
定义
实现代理模式的单例
首先创建一个CreateDiv的构造函数 只负责创建div
var CreateDiv = function(html) {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function(){
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div)
}
接下来引入代理类proxySingletonCreateDiv
var ProxysingletonCreateDiv = (function(){
var instance;
return function(html) {
if (!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
var a = new ProxysingletonCreateDiv('test1');
var b = new ProxysingletonCreateDiv('test2');
console.log(a === b) // true
通用的惰性单例
惰性单例是指在需要的时候才创建对象实例, 而不像之前的代码那样,利用自执行行数在代码执行时就把对象实例创建。
当我们点击登录按钮时,页面中可能出现一个弹框,而这个弹框是唯一的,无论点击多少次登录按钮,弹框只会被创建一次,那么这种情况下就适合用单例模式来创建弹框。
<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>
当打开一个网站时,需要登录,但登录的弹窗只会在点击登录按钮的时候出现,甚至有的网站不需要登录就能直接浏览。那么很有可能将白白浪费一些DOM节点。这时我们并不需要再页面加载时就创建一个弹窗。
正确的打开方式可以用下面的方式实现。
<html>
<body>
<button id="loginBtn">登录</button>
</body>
<script>
var createLoginLayer = (function(){
var div;
return function(){
if (!div) {
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 obj;
if (!obj) {
obj = xxx
}
现在我们局把如何管理单例的逻辑从原来的代码抽离出来,这些逻辑被封装在getSingle函数内部,创建对象的方法fn被当成参数动态传入getSingle函数:
var getSingle = function(fn) {
var result;
return function(){
return result || (result = fn.apply(this, arguments))
}
}
接下来将用于创建登录浮窗的方法用参数fn的形式传入getSingle,我们可以不仅传入createLoginLayer,还能传入createScript、createIframe等。之后再让getSingle返回一个新的函数,并且用一个变量result来保存fn的计算结果。result变量因为保存在闭包中,它通用的惰性单例永远不会被销毁。在将来的请求中,如果resutlt已经被赋值,那么它将返回这个值。代码如下。
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'
}
至此我们实现了一个getSingle函数来帮我们实现只有一个实例对象的目的,并且将实例对象要做的职责独立出来,两个方法互不干扰。
完整案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.prompt {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
font-size: 18px;
color: #ffffff;
background-color: #df8263;
border-radius: 3px;
padding: 10px 30px;
min-width: 100px;
}
.prompt p {
padding: 0;
margin: 0;
}
.prompt.default {
background-color: #df8263;
}
.prompt.warning {
background-color: #d9b131;
}
.prompt.success {
background-color: #31d952;
}
.prompt.fail {
background-color: #cab9b9;
}
.prompt-close {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
</style>
</head>
<body>
<button id="login">登录</button>
<script>
var PromptComponent = function () {
this.createDom = this.singlePrompt(this.createPrompt);
}
PromptComponent.prototype.init = function (params) {
var style = params.style || 'default',
message = params.message || '提示!',
duration = parseInt(params.duration) || 1500;
var promptDom = this.createDom(style, message);
promptDom.style.display = 'block';
setTimeout(function () {
promptDom.style.display = 'none';
}, duration)
}
PromptComponent.prototype.singlePrompt = function (fn) {
var instance;
return function () {
return instance || (instance = fn.apply(this, arguments));
}
}
PromptComponent.prototype.createPrompt = function (style, message) {
var div = document.createElement('div');
var strHtml = '<p>'+ message +'</p><span class="prompt-close">×</span>'
div.className = 'prompt ' + style;
div.innerHTML = strHtml;
div.style.display = 'none';
div.addEventListener('click', function () {
div.style.display = 'none';
}, false)
document.body.appendChild(div);
return div;
}
var warning = new PromptComponent();
document.getElementById('login').addEventListener('click', function () {
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({code: 200})
}, 1000)
}).then(res => {
if (res.code == 200) {
warning.init({
style: 'success',
message: '登陆成功'
})
} else {
warning.init({
style: 'fail',
message: '登陆失败'
})
}
})
}, false)
</script>
</body>
</html>