前端模版原理&实现(二)
【由简入深】
前端模版的本质就是一个可以转换函数(渲染函数)的字符串。渲染函数放进一个充满数据的对象后,还原为一个全新的字符串。因此重点是如何构建一个渲染函数。最简单的方式就是正则,下面是一个轻型的填充数据的方法:
function format(str, object) {
var array = Array.prototype.slice.call(arguments, 1);
return str.replace(/\\?\#{([^{}]+)\}/gm, function(match, name) {
if (match.charAt(0) == '\\')
return match.slice(1);
var index = Number(name)
if (index >= 0)
return array[index];
if (object && object[name] !== void 0)
return object[name];
return '';
});
}
format方法是通过#{ }来划分静态内容与动态内容的,一般来说它们称之为定界符(delimiter)。#{为前定界符,}为后界符,这个#{}其实是ruby风格的定界符。通常的定界符是<%与%>,{{与}} 。通常在前定界符中还有一些修饰符号,比如=号,表示这个会输出到页面,-号,表示会去掉两旁的空白。
将下例,要编译成一个渲染函数:
var tpl = '你好,我的名字啊{{name}}, 今年已经 {{info.age}}岁了'
var data = {
name: "司徒正美",
age: 20
}
大抵是这样:
var body = '你好,我的名字叫'+ data.name+ ', 今年已经 '+data.info.age+ '岁了'
var render = new Function('data', 'return '+ body)
var tpl = '你好,我的名字啊{{name}}, 今年已经 {{info.age}}岁了'
function tokenize(str){
var openTag='{{';
var closeTag='}}';
var ret = [];
do{
var index = str.indexOf(openTag);
index = index ===-1?str.length:index;
var value = str.slice(0,index);
// 抽取{{前面的静态内容
ret.push({
expr:value,
type:'text'
})
//改变str字符串自身
str = str.slice(index+openTag.length);
if(str){
index = str.indexOf(closeTag);
var value= str.slice(0,index);
//抽取{{与}}的动态内容
ret.push({
expr:value.trim(),//js逻辑两旁的空格可以省去
type:'js'
})
//改变str字符串自身
str = str.slice(index+closeTag.length);
}
} while(str.length)
return ret;
}
console.log(tokenize(tpl))

然后通过render方法将它们拼接起来
function render(str){
var tokens = tokenize(str);
var ret = [];
for(var i = 0,token;token=tokens[i++];){
if(token.type === 'text'){
ret.push('"'+token.expr+'"');
}else{
ret.push(token.expr);
}
}
return ret.join("+");
}
console.log(render(tpl))//"你好,我的名字啊"+name+", 今年已经 "+info.age+"岁了"
这个方法还不完整,首先是在两旁加上双引号是不可靠的,万一里面还有双引号怎么办,因此,我们引入quote方法,当类型为文本时,ret.push(+quote(token.expr)+)。其次需要对动态部分的变量加上.data。怎么知道它是一个变量呢,我们回想一下变量的定义,就是以_,$或字母开头的字符串组合。为了简洁起见,我们暂时不会用中文情况。不过,info.age这个字符串里面,其实有两个字符串变量的子串,而我只需要在info前面加上data.。这时,我们需要设法匹配变量前,将对象的子级属性替换掉,替换成不符合变量的字符串,然后再替换回去。为此,我搞了一个dig与fill方法,将子级属性变成??12这样的字符串:
var quote = JSON.stringify
var rident = /[$a-zA-Z_][$a-zA-Z0-9_]*/g
var rproperty = /\.\s*[\w\.\$]+/g
var number = 1
var rfill = /\?\?\d+/g
var stringPool = {}
function dig(a) {
var key = '??' + number++
stringPool[key] = a
return key
}
function fill(a) {
return stringPool[a]
}
function render(str) {
stringPool = {}
var tokens = tokenize(str)
var ret = []
for (var i = 0, token; token = tokens[i++]; ) {
if (token.type === 'text') {
ret.push(quote(token.expr))
} else {
// 先去掉对象的子级属性,减少干扰因素
var js = token.expr.replace(rproperty, dig)
js = js.replace(rident, function (a) {
return 'data.' + a
})
js = js.replace(rfill, fill)
ret.push(js)
}
}
console.log("return " + ret.join('+'))
}
render(tpl)
//return "你好,我的名字叫"+data.name+", 今年已经 "+data.info.age+"岁了"
未待完续~
参照原文:
https://segmentfault.com/a/1190000006990480
http://www.codeceo.com/article/javascript-template-engine.html
https://blog.csdn.net/yczz/article/details/49585381

浙公网安备 33010602011771号