js模版引擎开发实战以及对eval函数的改进

简介

  前段时间,想着自己写一个简单的模版引擎,便于自己平时开发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. 使用方法

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Document</title>
 6     <script src="utils/template.js"></script>
 7 </head>
 8 <body>
 9 <div id="test">
10
11 </div>
12     <script id="test_template" type="text/my_template">
13         <ul>
14             {{for(var i = 0; i < data.todos.length; ++i) { }}
15                 {{if(data.todos[i].todo_type) { }}
16                     <li>{{data.todos[i].todo_name}}</li>
17                 {{ } }}
18             {{ } }}
19         </ul>
20     </script>
21
22     <script>
23         var data = {
24             todos: [{
25                 todo_name: "eat",
26                 todo_type: "todo"
27             }, {
28                 todo_name: "sleep",
29                 todo_type: "completed"
30             }, {
31                 todo_name: "play",
32                 todo_type: "todo"
33             }]
34
35         };
36         var tpl = document.getElementById(‘test_template‘);
37
38         str = tpl.innerHTML;
39
40         template.complileTpl(str);
41
42         var root = document.getElementById(‘test‘);
43
44         template.executeTpl(root, data);
45     </script>
46 </body>
47 </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函数)。

时间: 2024-10-12 08:36:55

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

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

前段时间,想着自己写一个简单的模版引擎,便于自己平时开发demo时使用.于是根据自己对模版引擎的理解,定义自己的模版格式,然后,根据自己定义的格式,编写处理函数,将模版标签中的字符串,解析成可执行的字符串,然后再用eval函数执行该可执行的字符串. 然后问题就出现了!eval等价于evil! 为什么呢?各大js权威书籍上都不提倡使用eval.下面我详细的解释一下为什么不提倡. 首先,大家需要知道,js并不是一门解释型语言.它和其他大家熟知的编程语言(c,java,c++)一样,是编译型语言.但是

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

目前 m站开发的方式,依然请求完整的html,这样造成的问题就是每次请求的数据量过大过多,在没有wifi的情况下,导致页面打开的速度很慢,耗费的流量也较多:访问m站的多是移动端设备,其浏览器的版本都较高,所以其html5属性localstorage支持性也较好,并且m站页面较为简单,结构性较好,如果使用localstorage+js解析模版+json数据的方式来实现m的结构,其访问速度应该会大幅度提升. 具体实现方式如下: 1.把页面的html节点存储在localstorage中,因为m站结构简

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

一.为什么选择Handlebars.js 据小菜了解,对于java开发,涉及到页面展示时,比较主流的有两种解决方案: 1. struts2+vo+el表达式. 这种方式,重点不在于struts2,而是vo和el表达式,其基本思想是:根据页面需要的信息,构造出一个实体,这个实体中包含了界面需要的所有属性,通常这个实体是由N个表中的字段构成的,俗称vo.由于vo的属性可以是String.List.Map等等等,又可以vo套vo,因此这种方式非常灵活,也非常好用. 在后台对vo进行赋值,通过strut

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

json数据错误处理,把json文件数据复制到----> https://www.bejson.com/ 在线解析json 这样能直观的了解到是否是json数据写错,在控制台打断点,那里错误打那里断点,观察是否有错误. <!DOCTYPE html> <html> <head> <title>前后端数据交互处理原生JS模板引擎开发</title> <meta charset ='utf-8'> <script type=&

js模版引擎—nodetpl

这个是公司内部使用的一个模版引擎,主要应用在pc. 根据模版,返回一个js文件.大概是这个样子的js. (function(N, undefined){ var PATH = 'http://core.pc.lietou-static.com/tpls/common/plugins/localdata/city.js'; if(!N || !N._tpls) return false; N._tpls[PATH] = N._tpls[PATH] ||{......}; })(window.Nod

handlebars.js模版引擎随记

前台的模版引擎有许多种,相比较而言 我个人更觉得handlebars更为轻便 首先github上下载自新版本的handelbars.js http://handlebarsjs.com 下载下来之后呢 我们需要在页面引入js 1 <script type="text/javascript" src="script/jquery.js"></script> 2 <script type="text/javascript"

微信小程序开发之不能使用eval函数的问题

一 eval函数问题 JavaScript中的eval函数是颇受开发者争议的问题之一,问题主要在于其可能导致的不安全性.有关此方面问题,在此不再赘述,读者可能很容易地浏览到许多介绍性文章. 但是,eval函数的优点也是很明显的.例如,使用JS编写一个计算器程序,在遇到"2+1-3*5"这样的字符串时,使用eval就可以很容易地计算出,类似如: var s="2+1-3*5"; console.log(eval(s)); 二 微信小程序练手遇到问题 (1)微信小程序环

webpack4 使用pug模版引擎开发

项目地址 why 前几天有一个同事拿了几张html的文件问我怎么把公共的头部提取出来进行样式修改,我没有办法,也没查出该怎么解决,然后就思考. 怎么样才能实现多个页面共用相同的html代码呢? 于是有了这篇文章. 初始化 npm init -y 安装webpack npm install webpack webpack-cli --save-dev 安装pug-html-loader html-loader npm install pug-html-loader html-loader --sa

为什么要使用JS模板引擎

我之前在写一个输入联想控件的时候,改过好几个版本,每个版本不是因为性能不好就是因为代码凌乱而被推翻,最后用了understore模板引擎,效果有明显改善.整好这两天在研究互联网技术架构,发现很多的开发框架前端都是使用js模板引擎,感悟真的是大道至简,殊途同归啊,哈哈. 关于为什么使用js模板引擎,在博客园发现园友的一片文章<js模版引擎handlebars.js实用教程——为什么选择Handlebars.js>,该文已经做了详细解答.下面内容转自该文: 据小菜了解,对于java开发,涉及到页面