最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

简易js模板引擎写法

来源:动视网 责编:小采 时间:2020-11-27 20:11:56
文档

简易js模板引擎写法

简易js模板引擎写法:前面js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用。直到......年初的时候,我还在上个项目组,那时候代码规范是未经允许不能使用 【外部代码】,囧 。有了需求,那么就去写吧,但是后来因为一些原因没用上。后来分了
推荐度:
导读简易js模板引擎写法:前面js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用。直到......年初的时候,我还在上个项目组,那时候代码规范是未经允许不能使用 【外部代码】,囧 。有了需求,那么就去写吧,但是后来因为一些原因没用上。后来分了


前面

js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用。

直到......

年初的时候,我还在上个项目组,那时候代码规范是未经允许不能使用 【外部代码】,囧 。

有了需求,那么就去写吧,但是后来因为一些原因没用上。后来分了产线,自己搭了一套构建,用了几个月感觉挺爽,把这小段代码按照比较大众的规范重写,跟大家分享下。

https://github.com/shalldie/mini-tpl

语法

首先是选择模板语法,ejs语法是首选,因为大众,更无需去学习指令型模板引擎的那些东西。

如果写过 jsp 或者 asp/asp.net 的可以直接上手。

怎么用它?

我要这么用

<body>
 <p id="root"></p>
 <script id="tplContent" type="text/html">
 <ul>
 <% for(var i=0; i<data.length; i++){
 var item = data[i];
 if(item.age < 30){%>
 <li>我的名字是<%=item.name%>,我的年龄是<%=item.age%></li>
 <%}else{%>
 <li>my name is <%=item.name%>,my age is a sercet.</li>
 <%}%>
 <% } %>
 </ul>
 </script>
 <script src="../build/mini-tpl.min.js"></script>
 <script>
 var data = [{ name: 'tom', age: 12 }, { name: 'lily', age: 24 }, { name: 'lucy', age: 55 }];
 var content = document.getElementById('tplContent').innerHTML;
 var result = miniTpl(content, data);
 document.getElementById('root').innerHTML = result;
 </script>
 </body>

想要这么用,那么就分析一下怎么才能实现。

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)

也就是说:

  1. 可以用 new Function 来动态的创建一个函数,去执行某动态生成的函数定义js语句。

  2. 通过 new Function 生成的函数,作用域在全局。

  3. 那么传参有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<data.length; i++){ 
var item = data[i]; 
if(item.age < 30){%>
 <li>我的名字是<%=item.name%>,我的年龄是<%=item.age%></li>
 <%}else{%>
 <li>my name is <%=item.name%>,my age is a sercet.</li>
 <%}%>
 <% } %>

js 逻辑部分,由 <%%> 包裹, js 变量的占位,由 <%= %> 包裹,剩下的是普通的要拼接的html字符串部分。

也就是说,需要用正则找出的部分有3种:

  1. <%%> 逻辑部分的js内容

  2. <%=%> 占位部分的js内容

  3. 其它的纯文本内容

其中第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 = /<%(?!=)([\s\S]*?)%>/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(/\r?\n/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('\n'))(data);
 }

这样就完成了简易的模板引擎,不要觉得拼字符串慢。

在现代浏览器(IE8开始)中,特地对字符串的操作做了大量的优化,用 += 拼字符串,要比用数组 push 再 join 的方式快很多很多,即使放到IE7(IE6不清楚)中,我这里测试也是拼字符串快。。

文档

简易js模板引擎写法

简易js模板引擎写法:前面js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用。直到......年初的时候,我还在上个项目组,那时候代码规范是未经允许不能使用 【外部代码】,囧 。有了需求,那么就去写吧,但是后来因为一些原因没用上。后来分了
推荐度:
标签: 简单 js 模板引擎
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top