本文基于《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>
