A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

这个模板引擎实现的核心点是利用正则表达式来匹配到模板语法里面的变量和JS语句,再将这些匹配到的字段push到一个数组中,最后连接起来,用Function来解析字符串,最后将执行后的结果放到指定DOM节点的innerHTML里面。
但是这个模板引擎还是有很多不足,比如不支持取余运算,不支持自定义模板语法,也不支持if、for、switch之外的JS语句,缺少HTML实体编码。
恰好我这阵子也在看underscore源码,于是就参考了一下underscore中template方法的实现。
这个是我参考template后实现的模板,一共只有60行代码。
(function () {    var root = this;    var html2Entity = (function () {        var escapeMap = {            '&': '&amp;',            '<': '&lt;',            '>': '&gt;',            '"': '&quot;',            "'": '&#x27;',            '`': '&#x60;'        };        var escaper = function (match) {            return escapeMap[match];        };        return function (string) {            var source = "(" + Object.keys(escapeMap).join("|") + ")";            var regexp = RegExp(source), regexpAll = RegExp(source, "g");            return regexp.test(string) ? string.replace(regexpAll, escaper) : string;        }    }())    var escapes = {        '"': '"',        "'": "'",        "\\": "\\",        '\n': 'n',        '\r': 'r',        '\u2028': 'u2028',        '\u2029': 'u2029'    }    var escaper = /\\|'|"|\r|\n|\u2028|\u2029/g;    var convertEscapes = function (match) {        return "\\" + escapes[match];    }    var template = function (tpl, settings) {        var templateSettings = Object.assign({}, {            interpolate: /<%=([\s\S]+?)%>/g,            escape: /<%-([\s\S]+?)%>/g,            evaluate: /<%([\s\S]+?)%>/g,        }, template.templateSettings);        settings = Object.assign({}, settings);        var matcher = RegExp(Object.keys(templateSettings).map(function (key) {            return templateSettings[key].source        }).join("|") + "|$", "g")        var source = "", index = 0;        tpl.replace(matcher, function (match, interpolate, escape, evaluate, offset) {            source += "__p += '" + tpl.slice(index, offset).replace(escaper, convertEscapes) + "'\n";            index = offset + match.length;            if (evaluate) {                source += evaluate + "\n"            } else if (interpolate) {                source += "__p += (" + interpolate + ") == null ? '' : " + interpolate + ";\n"            } else if (escape) {                source += "__p += (" + escape + ") == null ? '' : " + html2Entity(escape) + ";\n"            }            return match;        })        source = "var __p = '';" + source + 'return __p;'        if (!settings.variable) source = "with(obj||{}) {\n" + source + "\n}"        var render = new Function(settings.variable || "obj", source);        return render    }    root.templateY = template}.call(this))复制代码转义我们知道,在字符串中有一些特殊字符是需要转义的,比如"'", '"',不然就会和预期展示不一致,甚至是报错,所以我们一般会用反斜杠来表示转义,常见的转义字符有\n, \t, \r等等。
但是这里的convertEscapes里面我们为什么要多加一个反斜杠呢?
这是因为在执行new Function里面的语句时,也需要对字符进行一次转义,可以看一下下面这行代码:
var log = new Function("var a = '1\n23';console.log(a)");log() // Uncaught SyntaxError: Invalid or unexpected token复制代码这是因为Function函数在执行的时候,里面的内容被解析成了这样。
var a = '123';console.log(a)复制代码在JS里面是不允许字符串换行出现的,只能使用转义字符\n。
正则表达式underscore中摒弃了用正则表达式匹配for/if/switch/{/}等语句的做法,而是使用了不同的模板语法(<%=%>和<%%>)来区分当前是变量还是JS语句,这样虽然需要用户自己区分语法,但是给开发者减少了很多不必要的麻烦,因为如果用正则来匹配,那么后面就无法使用类似{# #}和{{}}的语法了。这里正则表达式的重点是+?,+?是惰性匹配,表示以最少的次数匹配到[\s\S],所以我们/<%=([\s\S]+?)%>/g是不会匹配到类似<%=name<%=age%>%>这种语法的,只会匹配到<%=name%>语法。
replace这里我们用到了replace第二个参数是函数的情况。
var pattern = /([a-z]+)\s([a-z]+)/;var str = "hello world";str.replace(pattern, function(match, p1, p2, offset) {    // p1 is "hello"    // p2 is "world"    return match;})复制代码在JS正则表达式中,使用()包起来的叫着捕获性分组,而使用(?:)的叫着非捕获性分组,在replace的第二个参数是函数时,每次匹配都会执行一次这个函数,这个函数第一个参数是pattern匹配到的字符串,在这个里面是"hello world"。
p1是第一个分组([a-z]+)匹配到的字符串,p2是第二个分组([a-z]+)匹配到的字符串,如果有更多的分组,那还会有更多参数p3, p4, p5等等,offset是最后一个参数,指的是在第几个索引处匹配到了,这里的offset是0,因为是从一开始就刚好匹配到了hello world。
字符串拼接underscore中使用+=字符串拼接的方式代替了数组push的方式,这样是因为+=相比push的性能会更高。


【转载】仅作分享,侵删
作者:尹光耀
链接:https://juejin.im/post/5c0cdc2ef265da612577ef27



1 个回复

正序浏览
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马