资讯专栏INFORMATION COLUMN

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

superw / 1828人阅读

摘要:函数执行后应该返回最终编译的模板。到了这里,我们只需要创建函数并执行它。

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

但是这还不够好,数据是非常简单的对象,并且很容易使用object["property"]对象的中括号语法,去读取对象的值。

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

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

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

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

因为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代码。

var tpl = "

Hello, my name is <%this.name%>. I"m <%this.profile.age%> years old.

";

这怎么可能呢? 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

我们可以利用这个语法,在一行代码中,定义函数、参数和函数体。这正是我们需要的。

在利用这种语法,创建函数之前。

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

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

"

Hello, my name is " + this.name + ". I"m" + this.profile.age + " years old.

";

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

一般情况下,我们都是使用for循环去遍历数据。

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

return 
"My skills:" +
for(var index in this.skills) { +
"" +
this.skills[index] +
"" +
}

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

var r = [];
r.push("My skills:");
for(var index in this.skills) {
    r.push("");
    r.push(this.skills[index]);
    r.push("");
}
return r.join("");

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

function TemplateEngine(tpl, data) {

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

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

    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 = "

Hello, my name is <%this.name%>. I"m <%this.profile.age%> years old.

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

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

但是code的最终结果是

/*
var r=[];
r.push("

Hello, my name is "); r.push("this.name"); r.push(". I"m "); r.push("this.profile.age"); return r.join("");

Hello, my name is <%this.name%>. I"m <%this.profile.age%> years old.

*/

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

所以我们需要addCode函数做一个小小的改动

function TemplateEngine(tpl,data){

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

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

    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 = "

Hello, my name is <%this.name%>. I"m <%this.profile.age%> years old.

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

现在this可以正确指向执行对象了。

var r=[];
r.push("

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(/[
	
]/g, "")).apply(data);

我们不需要传入参数,这里我使用apply()方法,改变了作用域,现在this.name指向了data。

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

function TemplateEngine(tpl,data){

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

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

    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(/[	
	]/g, "")).apply(data);
}

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

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("");
r.push(this.skills[index]);
r.push("");
r.push(});
return r.join("");

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

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

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

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

function TemplateEngine(tpl,data){

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

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


    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(/[	
	]/g, "")).apply(data);
}

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

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


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

//"My skill:jshtmlcss"

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

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

我们可以直接将复杂的逻辑应用到模板中。例如

var template = 
"My skills:" + 
"<%if(this.showSkills) {%>" +
    "<%for(var index in this.skills) {%>" + 
    "<%this.skills[index]%>" +
    "<%}%>" +
"<%} else {%>" +
    "

none

" + "<%}%>"; 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(""); arr.push(this.skills[index]); arr.push(""); } arr.push(""); } else { arr.push("

none

"); } return arr.join(""); */ //"My skills:jshtmlcss"

最后,我进一步做了一些优化,最终版本如下

var TemplateEngine = function(templateStr, data) {
    var tplStrExtractPattern = /<%([^%>]+)?%>/g,
        jsKeyWordsExtractPattern = /(^( )?(for|if|else|swich|case|break|{|}))(.*)?/g,
        code = "var arr = [];
",
        cursor = 0,
        match;
    var addCode = function(templateStr, jsCode) {
        if(jsCode) {
            if(templateStr.match(jsKeyWordsExtractPattern)) {
                code += templateStr + "
";
            } else {
                code += "arr.push(" + templateStr + ");
";
            }
        } else {
            code += "arr.push("" + templateStr.replace(/"/g, """) + "");
";
        }
    }
    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(/[
	]/g, "")).apply(data);
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98678.html

相关文章

  • 浅谈web中前端模板引擎的使用

    摘要:置换型模板引擎的优点实现简单,缺点效率低,无法满足高负载的应用请求。用途百度词条模板引擎可以让网站程序实现界面与数据分离,业务代码与逻辑代码的分离,提升开发效率,良好的设计也提高了代码的复用性。前端模板的出现使得前后端分离成为可能。 模板引擎 模板引擎-百度词条 什么是模板引擎?(百度词条) 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据分离而产生的,它可以生成...

    妤锋シ 评论0 收藏0
  • 翻译_20代码创造JavaScript模板引擎(一)

    摘要:翻译行代码创造模板引擎一想看博客原文链接,请点击下方一个非常好用的学习正则表达的网站正则表达式图文解说网站译文事情的起因,我想编写一个逻辑简单的模板引擎,它可以很好满足我现在的需求。,表示全局匹配。 翻译_20行代码创造JavaScript模板引擎(一) 想看博客原文链接,请点击下方 JavaScript template engine in just 20 lines 一个非常好用...

    hiyang 评论0 收藏0
  • 小型的编程项目有哪些值得推荐?这本神书写了 22 个,个个了不得

    摘要:电子表格使用语言电子表格是办公软件的必备,我们最熟知的是微软的。文中用框架来实现一个简单的电子表格,所用代码仅行。 showImg(https://segmentfault.com/img/remote/1460000019770011); 本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/Ob... 今天,...

    haitiancoder 评论0 收藏0
  • 小型的编程项目有哪些值得推荐?这本神书写了 22 个,个个了不得

    摘要:电子表格使用语言电子表格是办公软件的必备,我们最熟知的是微软的。文中用框架来实现一个简单的电子表格,所用代码仅行。 showImg(https://segmentfault.com/img/remote/1460000019770011); 本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/Ob... 今天,...

    Bowman_han 评论0 收藏0
  • 小型的编程项目有哪些值得推荐?这本神书写了 22 个,个个了不得

    摘要:电子表格使用语言电子表格是办公软件的必备,我们最熟知的是微软的。文中用框架来实现一个简单的电子表格,所用代码仅行。 showImg(https://segmentfault.com/img/remote/1460000019770011); 本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/Ob... 今天,...

    sf_wangchong 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<