本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
大家有没有遇到过操作大量节点的情况,比如我们在页面上想要创建一个展示区域,这块区域,需要跟不同的选项来展示不同结构的内容,那我们就需要根据选项来操作不同的节点。首先创建节点,然后创建节点内容,将节点内容插入到创建的节点中,最后将节点插入到父节点中。
这一套操作下俩,一个节点就需要 4 步,如果节点比较多,那这性能消耗就有点遭不住了。
为了解决这一个问题,我们可以使用简单模板方法。如果各位接触过模板字符串,那学起来易如反掌。
简单模板模式
通过格式化字符串拼凑出视图避免创建视图时大量节点操作,优化内存开销。
举个例子,我们规定一个展示区容器,用来展示不同的信息。
//命名空间 单体对象
var A = A || [];
//主体展示容器区
A.root = document.getElementById('container');
//创建视图方法集合
A.strategy = {
'listPart': function() {
},
'codePart': function() {
},
'onlyTitle': function() {
},
'guide': function() {
}
//.......
}
//创建视图入口
A.init = function(data) {
//根据传输的试图类型创建视图
this.strategy[data.type](data);
}
接下来是实现 strategy 中的每一种创建算法。(下面代码粗略扫一眼就行)
//文字列表展示
'listPart': function(data) {
var s = document.createElement('div'), //模块容器
h = document.createElement('h2'), //标题容器
p = document.createElement('p'), //描述容器
ht = document.createTextNode(data.data.h2), //标题内容
pt = document.createTextNode(data.data.p), //描述内容
ul = document.createElement('ul'), //列表容器
ldata = data.data.li, //列表数据
//li 列表项容器, strong 列表项标题,span 列表项解释,t 列表项标题内容, c 列表项解释内容
li, strong, span, t, c;
//有 id 设置模块 id
data.id && (s.id = data.id);
s.className = 'part'; //设置模块类名
h.appendChild(ht); //将标题内容放到标题容器
p.appendChild(pt); //将描述内容放到描述容器
s.appendChild(h); //将标题容器插入模块容器
s.appendChild(p); //将描述容器插入模板容器
//遍历列表数据
for(var i = 0, len = ldata.length; i < len; i++) {
li = document.createElement('li'); //创建列表项容器
strong = document.createElement('strong'); //创建列表项标题容器
span = document.createElement('span'); //创建列表项解释容器
t = document.createTextNode(ldata[i].strong); //创建列表项标题内容
c = document.createTextNode(ldata[i].span); //创建列表项解释内容
strong.appendChild(t); //向列表项标题容器中插入标题内容
span.appendChild(c); //向列表项解释容器中插入解释类容
li.appendChild(strong); //向列表项中插入列表项标题
li.appendChild(span); //向列表项中插入列表项解释
ul.appendChild(li); //向列表项中插入列表项
}
s.appendChild(ul); //向模块中插入列表
A.root.appendChild(s); //展现模块
}
大家有没有觉得上面的代码,阅读起来总是看串行,虽然整整齐齐,但是看着就头大。这就是我们一开始说的操作各种节点。写起来费劲,读起来费劲,浏览器运行起来也费劲。
为了解决上面的问题,我们可以试着创建一个模板,用数据去格式化字符串来渲染视图并插入到容器里,这样实现的方案性能上会提高很多,首先需要一个渲染器,比如想用数据对象 ‘{demo: ‘this is a demo’}’ 去格式化 ‘<a>{#demo#}</a>+’ 字符串模板,得到 ‘<a>this is a demo</a>’ 渲染后的模板,便可插入到页面中,不过先要一个渲染模板引擎的方法 formateString 方法,将字符串模板中的 {#demo#} 替换成数据对象中的 demo 属性值。
//渲染模板方法
A.formateString = function(str, data) {
return str.replace(/\{#(\w+)#\}/g, function(match, key) {
return typeof data[key] === undefined ? '' : data[key]});
}
有了这个方法,我们就可以通过简单模板来渲染出我们需求的视图了。
//文字列表展示
'listPart': function(data) {
var s = document.creatElement('div'), //容器模块
ul = '', //列表字符串
ldata = data.data.li, //列表数据
//模块模板
tpl = [
'<h2>{#h2#}</h2>',
'<p>{#p#}</p>',
'<ul>{#ul#}</ul>'
].join(''),
//列表项模板
liTpl = [
'<li>',
'<strong>{#strong#}</strong>',
'<span>{#span#}</span>',
'</li>'
].join('');
//有 id 设置模块 id
data.id && (s.id = data.id);
//遍历列表数据
for(var i = 0, len = ldata.length; i < len; i++) {
//如果有列表项数据
if(ldata[i].em || ldata[i].span) {
//列表字符串追加一项列表项
ul += A.formateString(liTpl, ldata[i]);
}
}
//装饰列表数据
data.data.ul = ul;
//渲染模块并插入模块中
s.innerHTML = A.formateString(tpl, data.data);
//渲染模块
A.root.appendChild(s);
}
这样我们的模板就完成了,一行的渲染工作,便完成了上面那么多节点操作。不过这个版本还能优化, tpl 这个模板内部相似的地方还可以提取,这样我们创建一个模板生成器,我们只要传入不同的标签名就可以了。
//模板生成器 name: 标识
A.view = function(name) {
//模板库
var v = {
//代码模板
code: '<pre><code>{#code#}</code></pre>',
//图片模板
img: '<img src="{#src#}" alt="{#alt#}" title="{#title#}" />',
//带有id和类的模块模板
part: '<div id="{#id#}" class="{#class#}">{#part#}</div>',
//组合模板
theme: [
'<div>',
'<h1>{#title#}</h1>',
'{#content#}',
'</div>'
].join('')
}
//如果参数是一个数组,则返回多行模板
if(Object.prototype.toString.call(name) === '[object Array]') {
//模板缓存器
var tpl = '';
for(var i = 0, len = name.length; i < len; i++) {
//模板缓存器追加模板
tpl += arguments.callee(name[i]);
}
//返回最终模板
return tpl;
} else {
//如果模板库中有该模板则返回该模板,否则返回建议模板
return v[name] ? v[name] : ('<' + name + '>{#' + name +'#}</' + name + '>');
}
}
有了这个小型模板生成器,我们先获取一个模板就简单多了,比如我们想获取一个 ‘<span>{#span#}</span>’ ,就可以通过 A.view(‘span’) 方式来获取。有了它,完成我们的需求就容易多了。
//文字列表展示
'listPart': function(data) {
//......
//模块模板
tpl = A.view(['h2', 'p', 'ul']),
//列表项模板
liTpl = A.formateString(A.view('li'), {
li: A.view(['strong', 'span'])}),
//......
}
模板字符串的应用其实非常广泛,大家一定要好好熟悉这部分内容。