简介

  前段时间,想着自己写一个简单的模版引擎,便于自己平时开发demo时使用,同时也算是之前学习的知识的一种总结吧!

  首先我们先了解一下模版引擎的工作原理吧!

  1. 模版引擎其实就是将指定标签的内容根据固定规则,解析为可执行语句字符串;

  2. 执行可执行解析后的语句字符串,即生成我们想要的页面结构。

具体实现方法:

1. 最终效果

 1     /*  解析前
2 <ul>
3 {{for(var i = 0; i < data.todos.length; ++i)}}
4 {{if(data.todos[i].todo_type)}}
5 <li>{{data.todos[i].todo_name}}</li>
6 {{/if}}
7 {{/for}}
8 </ul>
9 */
10
11 /* 解析后
12 var str = "";
13 str += "<ul>";
14 for (var i = 0; i < data.todos.length; ++i) {
15 if (data.todos[i].todo_type) {
16 str += "<li>";
17 str += data.todos[i].todo_name;
18 str += "</li>";
19 }
20 }
21 str += "</ul>";
22 */
23
24 /* 执行后
25 <ul><li>eat</li><li>sleep</li><li>play</li></ul>
26 */

2.  整体分析

  1. 定义属于自己的模版引擎格式

  2. 创建一个全局对象,它包括存放编译后字符串的属性,编译和执行的函数方法,以及一些工具函数

3. 具体实现

  1. 自定义模版引擎格式

    1. 赋值 {{data}}
        2. 判断 {{if(...) { }} {{ } else if(...) { }} {{ } else { }} {{ } }}
        3. 对象 {{for(key in object) { }} {{ } }}
        4. 数组 {{for(var i = 0); i < arrays.length; ++i) { }} {{ } }}
        处理赋值以外,其他语句需要独占一行

  2. 定义全局对象

  全局对象中包括五个函数和一个字符串:其中complileTpl用于解析字符串,executeTpl用于运行解析生成的代码, jsStr用于存放解析生成的字符串,其他都是中间处理函数。

var template = {
// 存放解析后的js字符串
jsStr: "var str = '';", /**
* 将模版中的字符串解析为可执行的js语句
* @param {string} tpl 模版字符串
*/
complileTpl: function(tpl) {
}, /**
* 执行解析后的js语句
* @param {DOM对象} root 挂载对象
* @param {json} data 解析的数据对象
*/
executeTpl: function(root, data) {
}, /**
* 不包含指令行的处理函数
* @param {string} str 需要处理的字符串
*/
_handleLabel: function(str) {
}, /**
* 包含指令行的处理函数
* @param {string} str 需要处理的字符串
*/
_handleDirective: function(str) {
}, /**
* 处理字符串前后空白
* @param {string} str 需要处理的字符串
*/
_handlePadding: function(str) {
}
}

  3. 解析函数详解

  由于我是在mac上开发的,mac上'\n'表示换行。

  首先根据换行符,将标签中的字符串,分隔为数组。然后分别根据每一行中是否包含指令,进行不同的处理。

  如果不包含指令,创建一个将该字符串添加到存储字符串的变量jsStr中。

  如果包含指令,由于我设置了格式要求,只有赋值操作可以和html标签在同一行,其他的指令都要独占一样,所以,当为赋值情况下,将指令左右的标签元素作为字符串操作,添加到变量jsStr中,如过是其他指令,直接去掉{{}},添加到变量jsStr即可。

    /**
* 将模版中的字符串解析为可执行的js语句
* @param {string} tpl 模版字符串
*/
complileTpl: function(tpl) {
// 模版字符串按行分隔
var tplArrs = tpl.split('\n'); for (var index = 0; index < tplArrs.length; ++index) { var item = this._handlePadding(tplArrs[index]); // 处理不包含指令的行
if (item.indexOf('{{') == -1) {
this._handleLabel(item);
} else {
this._handleDirective(item);
}
}
},
/**
* 不包含指令行的处理函数
* @param {string} str 需要处理的字符串
*/
_handleLabel: function(str) {
// 去除空行或者空白行
if (str) {
this.jsStr += "str += '" + str + "';";
}
}, /**
* 包含指令行的处理函数
* @param {string} str 需要处理的字符串
*/
_handleDirective: function(str) {
// 处理指令前的字符串
var index = str.indexOf('{{');
var lastIndex = str.lastIndexOf('}}');
if (index == 0 && lastIndex == str.length - 2) {
this.jsStr += str.slice(index + 2, lastIndex);
} else if (index != 0 && lastIndex != str.length - 2) {
this.jsStr += "str += '" + str.slice(0, index) + "';";
this.jsStr += "str += " + str.slice(index + 2, lastIndex) + ";";
this.jsStr += "str += '" + str.slice(lastIndex + 2, str.length) + "';";
} else {
throw new Error('格式错误');
}
},     /**
     * 处理字符串前后空白
     * @param  {string} str 需要处理的字符串
     */
    _handlePadding: function(str) {
        return str.replace(/^\s*||\s*$/g, '');
    }

  4. 执行编译后的字符串语句

  使用eval运行编译后的字符串语句。

    /**
* 执行解析后的js语句
* @param {DOM对象} root 挂载对象
* @param {json} data 解析的数据对象
*/
executeTpl: function(root, data) {
var html = eval(this.jsStr);
console.log(html);
root.innerHTML = html;
},

  5. 使用方法

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="utils/template.js"></script>
</head>
<body>
<div id="test"> </div>
<script id="test_template" type="text/my_template">
<ul>
{{for(var i = 0; i < data.todos.length; ++i) { }}
{{if(data.todos[i].todo_type) { }}
<li>{{data.todos[i].todo_name}}</li>
{{ } }}
{{ } }}
</ul>
</script> <script>
var data = {
todos: [{
todo_name: "eat",
todo_type: "todo"
}, {
todo_name: "sleep",
todo_type: "completed"
}, {
todo_name: "play",
todo_type: "todo"
}] };
var tpl = document.getElementById('test_template'); str = tpl.innerHTML; template.complileTpl(str); var root = document.getElementById('test'); template.executeTpl(root, data);
</script>
</body>
</html>

4. 延伸

  eval等价于evil!

  为什么呢?各大js权威书籍上都不提倡使用eval。下面我详细的解释一下为什么不提倡。

  首先,大家需要知道,js并不是一门解释型语言。它和其他大家熟知的编程语言(c,java,c++)一样,是编译型语言。但是,它和其他的编译型语言又不完全一样。众所周知,C语言等是预编译的语言,它们可以编译成目标代码,移植到其他机器中运行。而js呢,它并不是一门预编译的语言,它的编译过程可能只在执行前一秒。但是,它确实在执行前进行了编译过程。

  然后,大家要了解一下,词法作用域。所谓的词法作用域,是指当前作用域,可以访问的变量。

  js编译过程,其实就是在将申明的变量添加当前词法作用域,并将其他代码编译成可执行代码。然而,在浏览器中,做了一些列的优化,可以通过静态代码分析,定位申明的变量和函数的位置,方便后续访问。然而,我们却可以通过eval函数,改变当前词法作用域。这样一样,浏览器所做的优化都将付诸一炬。当出现eval,浏览器做的最好的处理方式,就是不做任何处理。

  以上为为什么不提倡使用eval,下面我是如何规避eval函数!

  主要的思路是:我们经常使用script标签动态添加脚本文件,同样我们也可以通过script标签中添加可执行语句字符串,也就可以动态添加可执行语句。

代码如下:

 1 /**
2 * 将传入的可执行字符串,通过script标签执行
3 * @param {[string]} str 可执行字符串
4 */
5 function strToFun(str) {
6 // 创建script标签
7 var script = document.createElement('script');
8 script.id = 'executableString';
9
10 // 处理传入的字符串,当相应的语句执行完毕后,将script标签移除
11 var handleStr = '(function() { ' + str + ';var script = document.getElementById("executableString"); document.body.removeChild(script); })();';
12
13 // 将待执行的代码添加到刚创建的script标签中
14 script.innerHTML = handleStr;
15
16 // 将创建的脚本追加到DOM树中
17 document.body.appendChild(script);
18 }

  以上,只是我一时的想法,希望大家积极提供不同的想法!!!

  虽然上面在解决eval问题的同时,引入了DOM操作,可能没有改善性能,但是,这种方法是可以解决CSP(Content-Security-Policy)问题!!(CSP中可能会禁止使用eval函数)。  

js模版引擎开发实战以及对eval函数的改进的更多相关文章

  1. 如何在前端模版引擎开发中避免使用eval函数

    前段时间,想着自己写一个简单的模版引擎,便于自己平时开发demo时使用.于是根据自己对模版引擎的理解,定义自己的模版格式,然后,根据自己定义的格式,编写处理函数,将模版标签中的字符串,解析成可执行的字 ...

  2. 使用localstorage及js模版引擎 开发 m站设想

    目前 m站开发的方式,依然请求完整的html,这样造成的问题就是每次请求的数据量过大过多,在没有wifi的情况下,导致页面打开的速度很慢,耗费的流量也较多:访问m站的多是移动端设备,其浏览器的版本都较 ...

  3. js模版引擎handlebars.js实用教程——为什么选择Handlebars.js

    返回目录 据小菜了解,对于java开发,涉及到页面展示时,比较主流的有两种解决方案: 1. struts2+vo+el表达式. 这种方式,重点不在于struts2,而是vo和el表达式,其基本思想是: ...

  4. JS模版引擎[20行代码实现模版引擎读后感]

    曾经阅读过<只有20行JAVASCRIPT代码, 手把手教你写一个页面模版引擎>这篇文章, 对其中实现模版的想法实在膜拜, 于是有了这篇读后感, 谈谈自己对模版引擎的理解, 以及用自己的语 ...

  5. js模版引擎handlebars.js实用教程

    js模版引擎handlebars.js实用教程 阅读本文需要了解基本的Handlebars.js概念,本文并不是Handlebars.js基础教程,而是注重于实际应用,为读者阐述使用过程中可能会遇到的 ...

  6. 前后端数据交互处理基于原生JS模板引擎开发

    json数据错误处理,把json文件数据复制到----> https://www.bejson.com/ 在线解析json 这样能直观的了解到是否是json数据写错,在控制台打断点,那里错误打那 ...

  7. 简单的 js 模版引擎

    简单的 js 模版引擎 var tplEngine = function(tpl, data) { var reg = /<%([^%>]+)?%>/g, regOut = /(^( ...

  8. handlebars.js模版引擎随记

    前台的模版引擎有许多种,相比较而言 我个人更觉得handlebars更为轻便 首先github上下载自新版本的handelbars.js http://handlebarsjs.com 下载下来之后呢 ...

  9. js模版引擎handlebars.js实用教程——each-基本循环使用方法

    返回目录 <!DOCTYPE html> <html> <head> <META http-equiv=Content-Type content=" ...

随机推荐

  1. O(mn)实现LCIS

    序: LCIS即求两序列的最长公共不下降子序列.思路于LCS基本一致. 用dp[i][j]记录当前最大值. 代码实现: /* About: LCIS O(mn) Auther: kongse_qi D ...

  2. HDU4712 Hamming Distance (随机化)

    link:http://acm.hdu.edu.cn/showproblem.php?pid=4712 题意:给1e5个数字,输出这些数中,最小的海明码距离. 思路:距离的范围是0到20.而且每个数的 ...

  3. struts的package的name与namespace

    <struts> <constant name="struts.devMode" value="true"></constant& ...

  4. JAVA虚拟机系列文章

    本系列文章主要记录自己在学习<深入理解Java虚拟机-JVM高级特性与最佳实践>的知识点总结,文章内容都是基于周志明所著书籍的总结. 1.Java内存区域与溢出 2.垃圾收集器与内存分配策 ...

  5. 使用java对文件批量重命名

    有时候从网络上下载的电视剧或者动漫,名字上都会被该网站加上前缀或者后缀,如图: 那么处女座的同学就不同意了,不行,我就是想让它按照我的习惯方式命名!但是呢,一个个修改是不是特别麻烦,如果是上百个呢?如 ...

  6. 关于JS的return false

    之前真的不知道JS里的return false 还能跳出事件. 今天在修改BUG的时候,用到了这个,就去查了一下,为了加深记忆在此处做个总结. retrun true: 返回正确的处理结果. retu ...

  7. Nginx教程(一) Nginx入门教程

    Nginx教程(一) Nginx入门教程 1 Nginx入门教程 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like协议下发行.由 ...

  8. 使用Spring MVC构建REST风格WEB应用

    转自:http://fancy888.iteye.com/blog/1629120 对于运行在网络上的MIS系统而言,处理数据的是整个系统的主要任务,翻开程序我们可以看到,80%以上的代码都在处理数据 ...

  9. Python列表(一)

    列表由一系列特定顺序排列的元素组成,在python中使用[]来表示列表,并用,来进行元素分割. >>> name_list['alben', 'james', 'harden', ' ...

  10. mongo - 升级步骤

    升级步骤1. 关闭balancer登陆mongos,执行sh.stopBalancer(),或者 连接到mongos>use config>db.settings.update( { _i ...