跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

简易js模板引擎写法

前面 js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用。 直到...... 年初的时候,我还在上个项目组,那时候代码规范是 未经允许不能使用 【外部代码】 ,囧 。 有了需求,那么就去写吧,但是后来因为一些原因没用上。后来分了产线,自己搭了一套构建,用了几个月感觉挺爽,把这小段代码按照比较大众的规范重写,跟大家分享下。 https://github.com/shalldie/mini-tpl 语法 首先是选择模板语法,ejs语法是首选,因为大众,更无需去学习指令型模板引擎的那些东西。 如果写过 jsp 或者 asp/asp.net 的可以直接上手。 怎么用它? 我要这么用

想要这么用,那么就分析一下怎么才能实现。 new Function
1 const content = 'console.log("hello world");'; 2 3 let func = new Function(content); 4 5 func(); // hello world
new Function ([arg1[, arg2[, ...argN]],] functionBody)
functionBody 一个含有包括函数定义的
JavaScript语句的字符串
。 使用Function构造器生成的函数,并不会在创建它们的上下文中创建闭包;它们一般在全局作用域中被创建。 当运行这些函数的时候,它们只能访问自己的本地变量和全局变量,不能访问Function构造器被调用生成的上下文的作用域。(MDN) 也就是说: 可以用 new Function 来动态的创建一个函数,去执行某动态生成的函数定义js语句。 通过 new Function 生成的函数,作用域在全局。 那么传参有3种:
把变量放到全局(扯淡)
、
函数传参
、
用call/apply把值传给函数的this
。 最初我用的是 call 来传值,如今想了想不太优雅,换成了用参数传递。也就是这样:
const content = 'console.log(data);'; let func = new Function('data', content); func('hello world'); // hello world
到此为止,雏形有了。下面来拆分。 模板拆分 先看模板:
<% for(var i=0; i
  • 我的名字是<%=item.name%>,我的年龄是<%=item.age%>
  • <%}else{%>
  • my name is <%=item.name%>,my age is a sercet.
  • <%}%> <% } %>
    js 逻辑部分 ,由
    <%%>
    包裹, js 变量的占位 ,由
    <%= %>
    包裹,剩下的是普通的要拼接的 html字符串 部分。 也就是说,需要用正则找出的部分有3种:
    <%%>
    逻辑部分的js内容
    <%=%>
    占位部分的js内容 使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件 如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Andr​​oid友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Andr​​oid应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更 下载 其它的
    纯文本
    内容 其中第2项,js占位的部分,也属于拼接文本。所以可以放在一起,就是
    js部分
    ,
    拼接部分
    。 正则提取 当然是选择正则表达式啊! 这里先跟大家扩展一下关于伪数组方面的内容,以及浏览器的控制台如何看待伪数组: 不扯远,直接说结论: 只要有 int类型的 length属性 ,有 function类型 的 splice 属性。 那么浏览器就会认为他是一个数组。 如果里面的其它属性按照索引来排序,甚至还可以像数组里面的项那样在控制台展示出来。 这种判断方式叫 duck typing ,如果一个东西长得像鸭子,而且叫起来像鸭子,,,那么它就是鸭子  0_o 回到正文,这个需要多次从模板中,把 js逻辑部分 和 文本 依次提取出来。 对于每一次提取,都要获取提取出的内容,本次匹配最后的索引项(用于提起文本内容)。所以我选择了 RegExp.prototype.exec 。 举个例子,RegExp.prototype.exec 返回的是一个集合(伪数组),它的类型是这样的: 属性/索引描述
    [0]
    匹配的全部字符串
    [1],...[n]
    括号中的分组捕获
    index
    匹配到的字符位于原始字符串的基于0的索引值
    input
    原始字符串 通过这样,就可以拿到匹配到的 js 逻辑部分,并通过 index 和本次匹配到的内容,来获取每个js逻辑部分之间的文本内容项。 要注意,在全局匹配模式下,正则表达式会接着上次匹配的结果继续匹配新的字符串。
    /** * 从原始模板中提取 文本/js 部分 * * @param {string} content * @returns {Array<{type:number,txt:string}>} */ function transform(content) { var arr = []; //返回的数组,用于保存匹配结果 var reg = /<%(?!=)([sS]*?)%>/g; //用于匹配js代码的正则 var match; //当前匹配到的match var nowIndex = 0; //当前匹配到的索引 while (match = reg.exec(content)) { // 保存当前匹配项之前的普通文本/占位 appendTxt(arr, content.substring(nowIndex, match.index)); //保存当前匹配项 arr.push({ type: 1, //js代码 txt: match[1] //匹配到的内容 }); //更新当前匹配索引 nowIndex = match.index + match[0].length; } //保存文本尾部 appendTxt(arr, content.substr(nowIndex)); return arr; } /** * 普通文本添加到数组,对换行部分进行转义 * * @param {Array<{type:number,txt:string}>} list * @param {string} content */ function appendTxt(list, content) { content = content.replace(/ ? /g, "\n"); list.push({ txt: content }); }
    得到了js逻辑项 和 文本内容 ,就可以把他们拼在一起,来动态生成一个function。要注意的是,文本内容中,包含 js占位项,这个地方要转换一下。
    /** * 模板 + 数据 =》 渲染后的字符串 * * @param {string} content 模板 * @param {any} data 数据 * @returns 渲染后的字符串 */ function render(content, data) { data = data || {}; var list = ['var tpl = "";']; var codeArr = transform(content); // 代码分割项数组 for (var i = 0, len = codeArr.length; i < len; i++) { var item = codeArr[i]; // 当前分割项 // 如果是文本类型,或者js占位项 if (!item.type) { var txt = 'tpl+="' + item.txt.replace(/<%=(.*?)%>/g, function (g0, g1) { return '"+' + g1 + '+"'; }) + '"'; list.push(txt); } else { // 如果是js代码 list.push(item.txt); } } list.push('return tpl;'); return new Function('data', list.join(' '))(data); }
    这样就完成了简易的模板引擎,不要觉得拼字符串慢。 在现代浏览器(IE8开始)中,特地对字符串的操作做了大量的优化,用 += 拼字符串,要比用数组 push 再 join 的方式快很多很多,即使放到IE7(IE6不清楚)中,我这里测试也是拼字符串快。。。

    相关文章