《JavaScript设计模式》----张荣铭(五)
首先说一下什么是设计模式?以及我们为什么要学习设计模式?
设计模式的定义是:设计模式是在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
也可以通俗的理解为:设计模式在某个特定场景下都某种问题的解决方案。当然,这也就是为什么我们要学习设计模式的原因。本书将设计模式按照类型分成六大类
- 创建型设计模式
- 结构型设计模式
- 行为型设计模式
- 技巧型设计模式
- 架构型设计模式
5 架构型设计模式
5.1 同步模块模式
将复杂的系统分成高内聚、低耦合的模块,是系统开发变得可控、可维护、可拓展,提高模块的利用率
暂不举例,在下文的异步模块模式中举例
5.2 异步模块模式
将复杂的系统分成高内聚、低耦合的模块,是系统开发变得可控、可维护、可拓展,提高模块的利用率(竟然和同步模块模式一样?)
(function (F) {
// 模块缓存器。存储已创建模块
var moduleCache = {};
function getUrl(moduleName) {
// lib/ajax 变成lib/ajax.js
return String(moduleName).replace(/\.js$/g, '') + '.js'
}
function loadScript(src) {
var _script = document.createElement('script');
_script.type = 'text/javaScript';
_script.charset = 'UTF-8';
_script.async = true;
_script.src = src;
document.getElementsByTagName('head')[0].appendChild(_script);
}
function setModule(moduleName, params, callback) {
var _module, fn;
if (moduleCache[moduleName]) {
_module = moduleCache[moduleName];
_module.status = 'loaded';
_module.exports = callback ? callback.apply(_module, params) : null; // 模块接口
while (fn = _module.onload.shift()) {
fn(_module.exports)
}
} else {
callback && callback.apply(null, params);
}
}
function loadModule(moduleName, callback) {
var _module;
if (moduleCache[moduleName]) {
_module = moduleCache[moduleName];
if (_module.status === 'loaded') {
// 这个很重要,loadModule一定是异步的,effectiveJS 上的某一条建议有写,永远不要同步的调用异步函数,这非常重要
setTimeout(callback(_module.exports), 0);
} else {
// 缓存 加载完成时的回调函数
_module.onload.push(callback);
}
// 模块第一次被依赖引用
} else {
// 缓存该模块初始化信息
moduleCache[moduleName] = {
moduleName: moduleName,
status: 'loading',
exports: null, // 模块接口 (模块加载成功后要执行的回调)
onload: [callback]
};
loadScript(getUrl(moduleName));
}
}
/*
* @param 模块的url
* @param 模块的依赖
* @parma 模块的callback
*/
F.module = function (url, modDeps, modCallback) {
// console.log('--', url, new Date());
var args = Array.prototype.slice.call(arguments),
// !之前一直有个疑惑,有一些提供的API,中间有几个参数不是必填的,是怎么能正确的将参数对应到正确的位置的,现在看来是对arguments对象的分析
callback = args.pop(),
// 获取依赖
deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : [],
url = args.length ? args.pop() : null,
// 依赖模块序列
params = [],
// 未加载依赖模块数量统计
depsCount = 0,
i = 0,
len = deps.length;
// 如果存在依赖
if (len) {
// 遍历模块的依赖
for (i = 0; i < len; i++) {
// 闭包保存i
(function (i) {
// 增加未加载模块数量统计
depsCount++;
loadModule(deps[i],
function (mod) {
params[i] = mod;
// 由于这是加载完成的回调函数,依赖模块数量减1
depsCount--;
// 如果依赖的模块都加载完了
if (depsCount === 0) {
// 这个在模块缓存中修正状态 loding => loaded
setModule(url, params, callback);
}
})
})(i);
}
} else {
// 不存在依赖的情况 直接loaded 且依赖序列为空
setModule(url, [], callback);
}
}
})(function () {
// 这个函数创建了模块管理器F,并且把它加到了全局变量上,这个F也会传给闭包的形参
return window.F = {};
}());
// 在mainScript中使用他们
F.module(['lib/event', 'lib/dom'],
function (event, dom) {
event.on('demo',
'click',
function () {
dom.html('demo', 'success');
})
});
5.3 widget模式
(Web Widget指的是一块可以在任意页面中执行的代码块)Widget模式是指借用Web Widget思想将页面分解成部件,针对部件开发,最终组合成完整的页面
// 模拟数据
var data = {
tagCloud: [
{ is_selected: true, title: '这是一本设计模式书', text: '设计模式' },
{ is_selected: false, title: '这是一本HTML', text: 'HTML' },
{ is_selected: null, title: '这是一本CSS', text: 'CSS' },
{ is_selected: '', title: '这是一本javascript', text: 'javascript' },
]
}
/**
* 案例一:视图模块化,方式一:初始
*/
/*===============实现原理开始===========*/
/****
* 模板:
* <a href = '#' class = 'data-lang{%if(is_selected){%}selected{%}%}' value = '{%=value%}'>{%=text%}</a>
****/
/****
* 数据:
* {is_selected : true, value : 'zh', text : 'zh-text'}
****/
/****
* 输出结果:
* <a href = '#' class = 'data-lang selected' value = 'zh'>zh-text</a>
****/
/*===============实现原理结束===========*/
/*===============模板引擎模块开始===========*/
// 模板引擎模块
F.module('lib/template', function () {
/***
* 模板引擎,处理数据的编译模板入口
* @param str 模块容器id或者模板字符串
* @param data 渲染数据
**/
var _TplEngine = function (str, data) {
// 如果数据是数组
if (data instanceof Array) {
// 缓存渲染模板结果
var html = '';
// 数据索引
var i = 0;
// 数据长度
var len = data.length;
// 遍历数据
for (; i < len; i++) {
// 缓存模板渲染结果,也可以写成
// html += arguments.callee(str, data[i]) ;
html += _getTpl(str)(data[i]);
}
// 返回模板渲染最终结果
return html;
} else {
// 返回模板渲染结果
return _getTpl(str)(data);
}
};
/***
* 获取模板
* @param str 模板容器id,或者模板字符串
**/
var _getTpl = function (str) {
// 获取元素
var ele = document.getElementById(str);
// 如果元素存在
if (ele) {
// 如果是input或者textarea表单元素,则获取该元素的value值,否则获取元素的内容
var html = /^(textarea | input)$/i.test(ele.nodeName) ? ele.value : ele.innerHTML;
// 编译模板
return _compileTpl(html);
} else {
// 编译模板
return _compileTpl(str);
}
};
// 处理模板
var _dealTpl = function (str) {
// 左分隔符
var _left = '{%';
// 右分隔符
var _right = '%}';
// 显示转化为字符串
return String(str)
// 转义标签内的<如:<div>{%if(a<b)%}</div> -> <div>{%if(a<b)%}</div>
.replace(/</g, '<')
// 转义标签内的>
.replace(/>/g, '>')
// 过滤回车符,制表符,回车符
.replace(/[\r\t\n]/g, '')
// 替换内容
.replace(new RegExp(_left + '=(.*?)' + _right, 'g'), "',typeof($1) === 'undefined' ? '' : $1, '")
// 替换左分隔符
.replace(new RegExp(_left, 'g'), "');")
// 替换右分隔符
.replace(new RegExp(_right, 'g'), "template_array.push('");
};
/***
* 编译执行
* @param str 模板数据
**/
var _compileTpl = function (str) {
// 编译函数体
var fnBody = "var template_array=[];\nvar fn=(function(data){\nvar template_key='';\nfor(key in data){\ntemplate_key +=(''+key+'=data[\"'+key+'\"];');\n}\neval(template_key);\ntemplate_array.push('" + _dealTpl(str) + "');\ntemplate_key=null;\n})(templateData);\nfn=null;\nreturn template_array.join('') ;";
// 编译函数
return new Function('templateData', fnBody);
};
// 返回
return _TplEngine;
});
/*######fnBody分析开始######*/
// "// 声明template_array模板容器组
// var template_array = [] ; \n
// // 闭包,模板容器组添加成员
// var fn = (function(data) { \n
// // 渲染数据变量的执行函数体
// var template_key = '' ; \n
// // 遍历渲染数据
// for(key in data) { \n
// // 为渲染数据变量的执行函数体添加赋值语句
// template_key += ('' + key + '=data[\"'+ key +'\"] ;') ; \n
// } \n
// // 执行渲染数据变量函数
// eval(template_key) ; \n
// // 为模板容器组添加成员(注意,此时渲染数据将替换容器中的变量)
// template_array.push('"+ _dealTpl(str) +"') ; \n
// //释放渲染数据变量函数
// template_key = null ; \n
// // 为闭包传入数据
// })(templateData) ; \n
// // 释放闭包
// fn = null ; \n
// // 返回渲染后的模板容器组,并拼接成字符串
// return template_array.join('') ;"
/*######fnBody分析结束######*/
// 使用时,只需引用模板引擎模块依赖就可以
F.module(['lib/template', 'lib/dom'], function (template, dom) {
// 服务器端获取到data数据逻辑
// 创建组件视图逻辑
var str = template('demo_script', data);
dom.html('test', str);
// 组件其他交互逻辑
});
/*===============模板引擎模块结束===========*/
/*===============模板种类结束===========*/
// 自定义模板
var demo_tpl = [
'<div id="tag_cloud">',
'{% for(var i = 0; i < tagCloud.length; i++){',
' var ctx = tagCloud[i]; %}',
'<a href="#" class="tag_item {% if(ctx["is_selected"]){ %}',
'selected',
'{% } %}" title="{%=ctx["title"]%}">',
'{%=ctx["text"]%}',
'</a>',
'{% } %}',
'</div>'
].join('');
/*===============模板种类结束===========*/
通过对多个异步进程监听,来触发未来发生的动作
function Waiter() {
// 注册的容器,他们都有异步的事情,当他们的事情都做完后,执行回调
var dfd = [],
// 成功的回调
doneArr = [],
// 失败的回调
failArr = [],
slice = Array.prototype.slice,
that = this;
var Promise = function () {
this.resolved = false;
this.rejected = false;
};
Promise.prototype = {
resolve: function () {
this.resolved = true;
// 应该可以没有这句把,没有监控对象……那说明是即时resolve 也挺好啊,类似于es6的 Promise.resolve
// if (!dfd.length) return;
for (var i = dfd.length - 1; i >= 0; i--) {
// 与比或优先级高,这里是说如果失败或者还没有resolved的话,就返回
if (dfd[i] && !dfd[i].resolved || dfd[i].rejected) {
return;
}
dfd.splice(i, 1);
}
// 到这里数组肯定为0了,不为0的话前面return了或者还在循环
_exec(doneArr);
},
reject: function () {
this.rejected = true;
if (!dfd.length) return;
// 失败清除全部监控对象
dfd.splice(0);
_exec(failArr);
}
};
that.Deferred = function () {
return new Promise();
};
function _exec(arr) {
var i = 0, len = arr.length;
for (; i < len; i++) {
try {
arr[i] && arr[i]();
} catch (e) {
}
}
}
that.when = function () {
dfd = slice.call(arguments);
var i = dfd.length;
for (--i; i >= 0; i--) {
if (!dfd[i] || dfd[i].resolved || dfd[i].rejected || !dfd[i] instanceof Promise) {
dfd.splice(i, 1);
}
}
return that;
};
that.done = function () {
doneArr = doneArr.concat(slice.call(arguments));
return that;
};
that.fail = function () {
failArr = failArr.concat(slice.call(arguments));
return that;
}
}
var waiter = new Waiter();
function first(waiter) {
var dtd = waiter.Deferred();
setTimeout(function () {
console.log('first finish');
dtd.resolve();
},
500);
return dtd;
}
function second(waiter) {
var dtd = waiter.Deferred();
setTimeout(function () {
console.log('second finish');
dtd.resolve();
},
2000);
return dtd;
}
waiter.when(first(waiter), second(waiter)).done(function () {
console.log('all finished');
});
5.4 MVC模式
MVC即模型(Model)-视图(View)-控制器(Ctrl),用一种将业务逻辑、数据、视图分离的方式组织架构代码
暂不举例说明
5.5 MVP模式
MVC即模型(Model)-视图(View)-控制器(Presenter):view层不直接引用Model层内的数据,而是通过Presenter层实现对Model层内的数据访问。即所有层次的交互都发生在Presenter层中
暂不举例说明