0
点赞
收藏
分享

微信扫一扫

js实现简易版模板引擎


1、基础js

//还原html字
function htmlUnEscape(str) {
return str.replace(/$#39;|<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<';
case '>':
return '>';
case '"':
return '"';
case '&':
return '&';
case "$#39;":
return '\\';
}
});
}
//替换模板
function myTemplate(str) {
// 在一个 replace 完成全部替换
var [interpolate, evaluate] = [/<%=(.+?)%>/g, /<%(.+?)%>/g] // interpolate插值 和 evaluate语句 开始和结束
var matcher = new RegExp(`${interpolate.source}|${evaluate.source}|$`, 'g' ) //RegExp正则表达式
var index = 0,
p = ''
// 1. 处理JS的行终止符(\n),由于要把字符串代码通过new Function包装产生函数,其本质是解析传入的字符串,所以诸如`new Function("var a = 'ab\nc';")`直接报错,而是要`"var a = 'ab\\nc';"`,前者源代码文本就被换行了,导致了语法解析错误,JS一共有4个终止符,\r \n \u2028 \u2029,除了IE遵守这个标准,其他浏览器只把\n当作行终止符,其他都视作零宽空白字符
// 2. 处理单引号和反斜杠这两个特殊字符
// 最终:**把真正的 \r \n \u2028 \u2029 \' \\ 变成对应的文本形式,即从1个字符变成2个字符,从而防止这些特殊字符参与JS源代码的语法解析**
// 有点难理解的地方是 '\\': '\\' 和 '\'': '\''
// 这是因为单字符的 \' 和双字符的 \' 表现是一样的
var escapes = {
'\n': 'n',// 防止由于换行导致语法解析错误
'\r': 'r',// 防止由于换行导致语法解析错误
'\u2028': 'u2028',
'\u2029': 'u2029',
'\\': '\\',
"'": "'",
}
var escapeReg = /[\n\r\u2028\u2029\\']/g
var escapeChar = (match) => '\\' + escapes[match]
// 换了一种实现方式,不再使用数组,直接使用字符串拼接(效率高了)

str.replace(matcher, function (match, interpolate, evaluate, offset) {
// 正则对象的lastIndex属性只有在开启g标志且在regexp.exec和regexp.test方法有效,指定下次匹配的开始位置,此属性可读可写,如果方法没找到任何匹配则自动将它设置0,而在这里,用index来模拟lastIndex的作用
// 另外需要注意,matcher最后的`$`目的是匹配字符串结束位置,从而得到结束位置的offset,当`$`发生匹配时,match是空字符串,因为`$`是零宽断言,确实发生匹配但是没有匹配内容,故返回空字符串

// 使用slice方法取子字符串的副本,确保str保持不变
// 将本次匹配到的<%=xxx%>或<%xxx%>之前的文本进行特殊字符文本化
p += str.slice(index, offset).replace(escapeReg, escapeChar)

// 记录下次replace匹配的开始位置
index = offset + match.length

// 进行替换
// 这里巧妙利用了正则表达式的 捕获分组 和 或运算
// `/part1(group1)|part2(group2)|part3/g`这是上面matcher的结构,由于或运算的存在,只要三者之一匹配成功,整个正则表达式匹配成功,就会执行replace的回调函数,由于group1和group2必然要存在(因为它们写在正则表达式里面),那么其中某一个就得是undefined,如果是part3发生的匹配,那么group1和group2都是undefined
if (interpolate) {
p += `' + (${interpolate} || \'\') + '`
} else if (evaluate) {
p += `'; ${evaluate} p+='`
}

// 把匹配到的字符串原封不动地还回去,确保str保持不变
return match
})
// 给p拼上头部和尾部的代码
p = "var p = ''; with(data){ p+='" + p + "';} return p;"
// 可以在`new Function`包上try-catch语句,避免创建函数失败
return new Function('data', p)
}

2、模板中使用以上js,如果单独封装,请引入

<div id="app">

<ol>
<%for(let i = 0; i < users.length; i++ ){%>
<li>
<a href="<%=users[i].url%>">
<%=users[i].name%>
is
<%=users[i].age%>
years old.
</a>
<ul class="sub-menu">
<% if(users[i].age==1){%>
<%for(let i = 0; i < users.length; i++ ){%>
<li>
<a href="<%=users[i].url%>">
<%=users[i].name%>
is
<%=users[i].age%>
years old.
</a>
</li>
<% } %>
<% } %>
</ul>
</li>
<% } %>
</ol>


</div>
<script src="https://www.jq22.com/jquery/jquery-3.3.1.js"></script>

<script>
var userListView = htmlUnEscape($("#app").html());
// alert(userListView);
var userListData = [
{ name: 'nat', age: 1, url: 'http://localhost:3000/nat' },
{ name: 'jack', age: 22, url: 'http://localhost:3000/jack' },
]
var userListRender3 = myTemplate3(userListView)
// console.log('userListRender3', userListRender3)
var res3 = userListRender3({ users: userListData })
$("#app").html(res3);

</script>


举报

相关推荐

0 条评论