首頁  >  文章  >  web前端  >  簡易js模板引擎寫法

簡易js模板引擎寫法

一个新手
一个新手原創
2017-10-20 10:30:032414瀏覽

前面

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: &#39;tom&#39;, age: 12 }, { name: &#39;lily&#39;, age: 24 }, { name: &#39;lucy&#39;, age: 55 }];
            var content = document.getElementById(&#39;tplContent&#39;).innerHTML;
            var result = miniTpl(content, data);
            document.getElementById(&#39;root&#39;).innerHTML = result;
        </script>
    </body>

想要這麼用,那就分析一下怎麼才能實現。

new Function

    1 const content = &#39;console.log("hello world");&#39;;    
    2 
    3 let func = new Function(content);    
    4 
    5 func(); // hello world
new Function ([arg1[, arg2[, ...argN]],] functionBody)

functionBody#  一個含有包含函數定義的JavaScript語句的<strong>字串</strong>#。

使用Function建構器產生的函數,並不會在建立它們的上下文中建立閉包;它們一般在全域作用域中被建立。
當執行這些函數的時候,它們只能存取自己的本地變數和全域變量,不能存取Function建構器被呼叫產生的上下文的作用域。 (MDN)

也就是說:

  1. 可以用 new Function 來動態的建立一個函數,去執行某動態產生的函數定義js語句。

  2. 透過 new Function 產生的函數,作用域在全域。

  3. 那麼傳參有3種:把變數放到全域(扯淡)函數傳參有3種:把變數放到全域(扯淡)函數傳參

  4. 用call/ apply把值傳給函數的this

最初我用的是 call 來傳值,如今想了想不太優雅,換成了用參數傳遞。也就是這樣:

const content = &#39;console.log(data);&#39;;
    
    let func = new Function(&#39;data&#39;, content);
    
    func(&#39;hello world&#39;); // 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 邏輯部分,由a4558806285d5b7820bdaa0b90aa4d26 包裹, js 變數的佔位,由332000003288cabbdff89f9a8e5a919b

包裹,剩下的是普通的要拼接的

html字串

部分。
  1. 也就是說,需要用正規找出的部分有3種:

  2. #a4558806285d5b7820bdaa0b90aa4d26
  3. 邏輯部分的js內容

  4. 4eed03230346664e54006e16f8581c1e
  5. 佔位部分的js內容

    其它的
  6. 純文本
內容

其中第2項,js佔位的部分,也屬於拼接文字。所以可以放在一起,就是 js部分

拼接部分

正規提取

當然是選擇正規表示式啊!

這裡先跟大家擴充一下關於偽數組方面的內容,以及瀏覽器的控制台如何看待偽陣列:

不扯遠,直接說結論:#只要有 int類型的length屬性

,有

function類型的splice

屬性。 那麼瀏覽器就會認為他是一個陣列。

如果裡面的其它屬性按照索引來排序,甚至還可以像陣列裡面的項目一樣在控制台展示出來。

 這個判斷方式叫做

duck typing

,如果一個東西長得像鴨子,而且叫起來像鴨子,,,那麼它就是鴨子 0_o

#回到正文,這個需要多次從模板中,把js邏輯部分和文字依序提取出來。

對於每一次提取,都要獲取提取出的內容,本次匹配最後的索引項目(用於提起文本內容)。所以我選擇了 RegExp.prototype.exec 。

舉個例子,RegExp.prototype.exec 回傳的是一個集合(偽數組),它的型別是這樣的:屬性/索引描述匹配的全部字串#括號中的分組捕獲符合到的字元位於原始字串的基於0的索引值原始字串
[0]
[1],...[n]
#index
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 = [&#39;var tpl = "";&#39;];
        var codeArr = transform(content);  // 代码分割项数组

        for (var i = 0, len = codeArr.length; i < len; i++) {
            var item = codeArr[i]; // 当前分割项

            // 如果是文本类型,或者js占位项
            if (!item.type) {
                var txt = &#39;tpl+="&#39; +
                    item.txt.replace(/<%=(.*?)%>/g, function (g0, g1) {
                        return &#39;"+&#39; + g1 + &#39;+"&#39;;
                    }) + &#39;"&#39;;
                list.push(txt);
            }
            else {  // 如果是js代码
                list.push(item.txt);
            }
        }
        list.push(&#39;return tpl;&#39;);

        return new Function(&#39;data&#39;, list.join(&#39;\n&#39;))(data);
    }

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

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

以上是簡易js模板引擎寫法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn