简介

  前段时间,想着自己写一个简单的模版引擎,便于自己平时开发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. iOS刨根问底-深入理解RunLoop

    开源的RunloopRef 通常所说的RunLoop指的是NSRunloop或者CFRunloopRef,CFRunloopRef是纯C的函数,而NSRunloop仅仅是CFRunloopRef的OC ...

  2. 初识Eclipse!!

      Java之路 ——初识Eclipse   零.大纲 一.前言 二.获取Eclipse 三.运行Eclipse 四.创建及运行第一个Java Project 五.界面介绍 六.如何调试 七.获取插件 ...

  3. Asp.Net Core 中获取应用程序物理路径(Getting the Web Root Path and the Content Root Path in ASP.NET Core)

    如果要得到传统的ASP.Net应用程序中的相对路径或虚拟路径对应的服务器物理路径,只需要使用使用Server.MapPath()方法来取得Asp.Net根目录的物理路径,如下所示: // Classi ...

  4. Java内存使用量测试

    JVM内存使用量测试测试各种不同的数据结构在JVM中的内存使用量 import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import java.lang. ...

  5. Java中的Object、T(泛型)、?区别

    因为最近重新看了泛型,又看了些反射,导致我对Object.T(以下代指泛型).?产生了疑惑. 我们先来试着理解一下Object类,学习Java的应该都知道Object是所有类的父类,注意:那么这就意味 ...

  6. Visual Studio2017中如何让Entity Framework工具【ADO.NET实体数据模型】支持MYSQL数据源

    熟悉Entity Framework应该对以下图片不陌生,他就是ADO.NET实体数据模型向导:可以将数据库的表自动生成模型类,或者创建Code First的模型文件. 但是这个模型向导默认只显示微软 ...

  7. 【JavaScript中的this详解】

    前言 this用法说难不难,有时候函数调用时,往往会搞不清楚this指向谁?那么,关于this的用法,你知道多少呢? 下面我来给大家整理一下关于this的详细分析,希望对大家有所帮助! this指向的 ...

  8. 多媒体开发库 之 SDL 详解

    SDL 简介 SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成.SDL提供了数种控制图像.声音.输出入的函数,让开发者只要用相同或是相似的 ...

  9. Warning: session_start() [function.session-start]: Cannot send session cache limiter

    Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers alrea ...

  10. jquery attr处理checkbox / select 等表单元素时的坑

    先上html结构 <body> <form action=""> <input type="checkbox" id=" ...