翻译_只需20行代码创造JavaScript模板引擎(二)

上文连接翻译_只需20行代码创造JavaScript模板引擎(一)

可是这还不够好,数据是很是简单的对象,而且很容易使用object['property']对象的中括号语法,去读取对象的值。css

但在实践中,咱们用到的数据中,可能有复杂的嵌套对象。html

//嵌套对象
data = {
    name: "Krasimir Tsonev",
    profile: {age:29}
}

若是有复杂的嵌套对象,就不能用对象的中括号语法读取值了。正则表达式

因此String.prototype.replace(matchedStr, data["profile.age"]) 就行不通了。segmentfault

由于data["profile.age"],每次返回undefined。数组

//对象的中括号语法读取值 object["property"]

var obj = {
    name: "Shaw",
    age: 18
}

console.log(obj["name"]); //"Shaw"
console.log(obj["age"]); // 18  


//复杂的嵌套对象,就不能用对象的中括号语法读取值了。

var obj = {
    name: "Shaw",
    profile: {
        age: 18
    }
}

console.log(obj["profile.age"]); // undefined

那么,怎么解决这个问题?
最好的办法是在模板中<%和%>之间放置真正的JavaScript代码。app

var tpl = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';

这怎么可能呢? John使用了new Function()语法, 没有显式的声明函数。函数

var fn = new Function("arg", "console.log(arg+1);");
fn(2); // 3

//fn是一个能够传入一个参数的函数
//fn函数体内的语句,就是 console.log(arg+1);

/* 等价于*/

function fn(arg) {
    console.log(arg +1);
}

fn(2); //3

咱们能够利用这个语法,在一行代码中,定义函数、参数和函数体。这正是咱们须要的。优化

在利用这种语法,建立函数以前。this

咱们必须设计好,函数体怎么写。函数执行后应该返回最终编译的模板。prototype

回想一下,咱们常常使用的字符窜拼接方法。

"<p>Hello, my name is " +
this.name +
". I\'m" +
this.profile.age +
" years old.</p>";

这说明,咱们能够把字符窜模板里面的内容拆解,拆解为html和JavaScript代码。

通常状况下,咱们都是使用for循环去遍历数据。

// 传入的字符窜模块
var template =
'My Skill:' +
'<%for(var index in this.skills) {%>' +
'<a href=""><%this.skills[index]%></a>' +
'<%}%>';
// 预想的返回结果

return 
'My skills:' +
for(var index in this.skills) { +
'<a href="">' +
this.skills[index] +
'</a>' +
}

固然,这将产生一个错误。这就是为何我决定遵循约翰文章中使用的逻辑,把全部的字符串放到一个数组中。

var r = [];
r.push('My skills:');
for(var index in this.skills) {
    r.push('<a href="">');
    r.push(this.skills[index]);
    r.push('</a>');
}
return r.join('');

下一个逻辑步骤是收集定制生成函数的不一样行。
咱们已经从模板中提取了一些信息。咱们知道占位符的内容和它们的位置。因此,经过使用一个辅助变量(游标)

function TemplateEngine(tpl, data) {

    var tplExtractPattern = /<%([^%>]+)?%>/g,
        code = 'var r=[];\n',
        cursor = 0,
        match;

    function addCode(line) {
        code += 'r.push("' + line.replace(/"/g,'\\"') +'"); \n';
    }

    while(match = tplExtractPattern.exec(tpl)) {
        addCode(tpl.slice(cursor, match.index));
        addCode(match[1]);
        cursor = match.index + match[0].length;
    }

    code += 'return r.join("");';

    console.log(code);

    return tpl;

}

var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';

TemplateEngine(template, {
    name: "Shaw",
    profile: { age: 18 }
});

变量code保存着函数体的代码。
在while循环语句中,咱们也须要变量cursor游标,告诉咱们字符窜slice()方法截取的起始坐标和末尾坐标。
变量code在while循环语句中,不断的拼接。

可是code的最终结果是

/*
var r=[];
r.push("<p>Hello, my name is "); 
r.push("this.name"); 
r.push(". I'm "); 
r.push("this.profile.age"); 
return r.join("");
<p>Hello, my name is <%this.name%>. I'm <%this.profile.age%> years old.</p>
*/

这不是咱们想要的。 "this.name" 和 "this.profile.name" 不该该被引号包裹。

因此咱们须要addCode函数作一个小小的改动

function TemplateEngine(tpl,data){

    var tplExtractPattern = /<%([^%>]+)?%>/g,
        code = 'var r=[];\n',
        cursor = 0,
        match;

    function addCode(line, js) {
        if(js) {
            code += 'r.push(' + line + ');\n';
        } else {
            code += 'r.push("' + line.replace(/"/g,'\\"') + '");\n';
        }
    }

    while(match = tplExtractPattern.exec(tpl)) {
        addCode(tpl.slice(cursor, match.index));
        addCode(match[1], true);
        cursor = match.index + match[0].length;
    }

    code += 'return r.join("");';

    console.log(code);

    return tpl;

}

var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';

TemplateEngine(template, {
    name: "Shaw",
    profile: { age: 18 }
});

如今this能够正确指向执行对象了。

var r=[];
r.push("<p>Hello, my name is ");
r.push(this.name);
r.push(". I'm ");
r.push(this.profile.age);
return r.join("");

到了这里,咱们只须要建立函数并执行它。
在TemplateEngine函数里把return tpl 替换成

return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);

咱们不须要传入参数,这里我使用apply()方法,改变了做用域,如今this.name指向了data。

几乎已经完成了。可是咱们还须要支持更多JavaScript关键字,好比if/else,循环流程语句。
让咱们从相同的例子,再次进行构思。

function TemplateEngine(tpl,data){

    var tplExtractPattern = /<%([^%>]+)?%>/g,
        code = 'var r=[];\n',
        cursor = 0,
        match;

    function addCode(line, js) {
        if(js) {
            code += 'r.push(' + line + ');\n';
        } else {
            code += 'r.push("' + line.replace(/"/g,'\\"') + '");\n';
        }
    }

    while(match = tplExtractPattern.exec(tpl)) {
        addCode(tpl.slice(cursor, match.index));
        addCode(match[1], true);
        cursor = match.index + match[0].length;
    }

    code += 'return r.join("");';

    console.log(code);

    return new Function(code.replace(/[\t\n\t]/g, '')).apply(data);
}

var template = 
'My skill:' +
'<%for(var index in this.skills) {%>' +
'<a href="#"><%this.skills[index]%></a>' +
'<%}%>';

TemplateEngine(template, {
    skills: ["js", "html", "css"]
}); 
// Uncaught SyntaxError: Unexpected token for

调用TemplateEngine(),控制台报错了,Uncaught SyntaxError: Unexpected token for。
在控制台打印出,拼接的代码

var r=[];
r.push("My skill:");
r.push(for(var index in this.skills) {);
r.push("<a href=\"#\">");
r.push(this.skills[index]);
r.push("</a>");
r.push(});
return r.join("");

带有for循环的语句不该该被直接放到数组里面,而是应该做为脚本的一部分直接运行。因此在把代码语句添加到code变量以前还要多作一个判断。

var re = /<%([^%>]+)?%>/g,
    reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
    code = 'var Arr = [];\n',
    cursor = 0;

function addCode(line,js) {
    if(js){
        if(line.match(reExp)) {
            code += line +'\n';
        } else {
            code += 'r.push(' + line + ');\n';
        }
    } else {
        code += 'r.push("' + line.replace(/"/g,'\\"')) + '");\n';
    }
}

添加一个新的正则表达式。它会判断代码中是否包含if、for、else等关键字。
若是有的话就直接添加到脚本代码中去,不然就添加到数组中去。

function TemplateEngine(tpl,data){

    var tplExtractPattern = /<%([^%>]+)?%>/g,
    jsExtractReExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
    code = 'var arr = [];\n',
    cursor = 0,
    match;

    function addCode(line,js) {
        if(js){
            if(line.match(jsExtractReExp)) {
                code += line +'\n';
            } else {
                code += 'arr.push(' + line + ');\n';
            }
        } else {
            code += 'arr.push("' + line.replace(/"/g,'\\"') + '");\n';
        }
    }


    while(match = tplExtractPattern.exec(tpl)) {
        addCode(tpl.slice(cursor, match.index));
        addCode(match[1], true);
        cursor = match.index + match[0].length;
    }

    code += 'return arr.join("");';

    console.log(code);

    return new Function(code.replace(/[\t\n\t]/g, '')).apply(data);
}

var template = 
'My skill:' +
'<%for(var index in this.skills) {%>' +
'<a href="#"><%this.skills[index]%></a>' +
'<%}%>';

TemplateEngine(template, {
    skills: ["js", "html", "css"]
});


/*
var arr = [];
arr.push("My skill:");
for(var index in this.skills) {
arr.push("<a href=\"#\">");
arr.push(this.skills[index]);
arr.push("</a>");
}
return arr.join("");
*/

//"My skill:<a href="#">js</a><a href="#">html</a><a href="#">css</a>"

一切都是正常编译的 :)。

最后的修改,实际上给了咱们更强大的处理能力。

咱们能够直接将复杂的逻辑应用到模板中。例如

var template = 
'My skills:' + 
'<%if(this.showSkills) {%>' +
    '<%for(var index in this.skills) {%>' + 
    '<a href="#"><%this.skills[index]%></a>' +
    '<%}%>' +
'<%} else {%>' +
    '<p>none</p>' +
'<%}%>';
console.log(TemplateEngine(template, {
    skills: ["js", "html", "css"],
    showSkills: true
}));

/*
var arr = [];
arr.push("My skills:");
if(this.showSkills) {
arr.push("");
for(var index in this.skills) {
arr.push("<a href=\"#\">");
arr.push(this.skills[index]);
arr.push("</a>");
}
arr.push("");
} else {
arr.push("<p>none</p>");
}
return arr.join("");
*/

//"My skills:<a href="#">js</a><a href="#">html</a><a href="#">css</a>"

最后,我进一步作了一些优化,最终版本以下

var TemplateEngine = function(templateStr, data) {
    var tplStrExtractPattern = /<%([^%>]+)?%>/g,
        jsKeyWordsExtractPattern = /(^( )?(for|if|else|swich|case|break|{|}))(.*)?/g,
        code = 'var arr = [];\n',
        cursor = 0,
        match;
    var addCode = function(templateStr, jsCode) {
        if(jsCode) {
            if(templateStr.match(jsKeyWordsExtractPattern)) {
                code += templateStr + '\n';
            } else {
                code += 'arr.push(' + templateStr + ');\n';
            }
        } else {
            code += 'arr.push("' + templateStr.replace(/"/g, '\\"') + '");\n';
        }
    }
    while(match = tplStrExtractPattern.exec(templateStr)){
        addCode(templateStr.slice(cursor, match.index));
        addCode(match[1], true);
        cursor = match.index + match[0].length;
    }
    code += 'return arr.join("");';
    return new Function(code.replace(/[\r\n\t]/g, '')).apply(data);
}
相关文章
相关标签/搜索