js模版引擎開發實戰以及對eval函數的改進

來源:互聯網
上載者:User

標籤:end   引入   alt   bar   範圍   his   定義   charset   編譯型   

簡介

  前段時間,想著自己寫一個簡單的模版引擎,便於自己平時開發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函數)。

js模版引擎開發實戰以及對eval函數的改進

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.