全球最快的JS模板引擎:tppl


廢話不多說,先上測試:

親測請訪問:【在線測試地址】單次結果不一定准確,請多測幾次。

tppl 的編譯渲染速度是著名的 jQuery 作者 John Resig 開發的 tmpl 的 43 倍!與第二名 artTemplate 也有一倍的差距。 

似乎每一個大公司都選擇自己開發模板引擎並將其開源,結果就是社區充斥着數不清的引擎,讓人眼花繚亂無從選擇。隨着時間的流逝,越來越多的功能被添加進去,最終讓一個強悍的發動機變成了一台臃腫復雜零件生銹的拖拉機。天吶,我就想網頁面里插一段 html,你居然要我往每個js文件里再塞進500行代碼!

不,事情原本應該更簡單。保持代碼的簡潔高效也意味着讓生活更加健康愉悅。

好吧,滿腦子裝着“封裝”或者“模塊化”的讀者估計有不同的看法。

下面我們來談談如何讓引擎更加強勁高效。

模板引擎分為兩大主要陣營:

    一、原生語法

    二、自定義模板語法

此兩種方案各有各的好,自定義語法相比前者的優點在於,看起來和寫起來更加規范簡潔,更“像”是一種模板。也能更好的配合編譯,並且可以避免用戶寫出“性能不佳”的代碼。部分人認為自定義語法對頁面設計人員來說更為友好,這就見仁見智了。而缺點就是通常只能自定義 if else 和 for 循環等簡單而有限的邏輯結構,不夠強大和靈活。

自定義語法的優化方法有隨着語法的不同而不同,但通常最終都是將其轉換為原生的語言邏輯結構。這里主要討論原生語法模板引擎的優化。

對於追求性能的模板引擎來說,有兩個顯而易見的方向:

    一、編譯結果緩存

    二、編譯結果靜態化

緩存很好理解,一次編譯多次渲染。通常是保存初步正則替換后的字符串中間值,重復渲染時直接拿來使用。

靜態指的是,編譯模板字符串生成一個字符串拼接的函數,而不是每次創建函數。渲染操作就相當於一次函數調用,代入變量完成字符串拼接並返回。一般引擎優化到這一步時,渲染方面已經沒有太大的差距和進步的空間。大家都成了字符串拼接函數,只能在微小的語法層面做優化。這里說一下,相信從事過前端開發的朋友,都“聽說”過一個字符串拼接的“快速方法”:將字符串片段 push 進一個數組,最后再 join 返回,性能比直接采用 + 或者 += 字符串要好。注意,這種方法過時了!在現代瀏覽器以及Node.js中已經不再成立。通過數組連接字符串只是一個“臨時解決方案”,隨着各大js編譯器的優化和進步,直接采用 + 字符串操作,給了一個編譯器在語言底層做出優化的機會,大家還是着眼於未來吧。

渲染差距不大時,編譯則還存在很多“水分”可以擠出來。當然,一部分模板引擎集成了諸如文件加載、Ajax等高級功能,對其性能方面有要求太高似乎也不太合理。

一般原生語法模板引擎,都采用類似下面的字符串表示:

<h1> <%=title %> </h1>
<% for(var i in content){ %>
  <p>第<%=i%>段:<%=content[i]%></p>
<%}%>

而編譯操作就是將這一段字符串轉換成類似下面的函數:

function(data){
    var str = "<h1>  "+data.title+"  </h1>";
    for(var i in data.content){
        str += "<p>第"+i+"段:"+data.content[i]+"</p>";
    }
    return str;
}

通常情況都是改變字符串結構,去掉模板標簽,再使用 new Function() 創建函數。

傳統傳遞參數的實現通過遍歷數據對象,把對象的名值分離,然后分別把對象成員名稱作為new Function的參數名(即變量名),然后使用函數的appley調用方式傳給那些參數。

tmpl 則使用了javascript不常用的 with 語句實現。 實現方式很簡潔,省去了var這個關鍵字。tmpl 性能問題就出在 with 上面。javascript 提供的 with 語句,本意是想用來更快捷的訪問對象的屬性。不幸的是,with語句在語言中的存在,就嚴重影響了 javascript 引擎的速度,因為它阻止了變量名的詞法作用域綁定。

而轉換的方法有很多種,一部分采用 split() 截斷分析,一部分采用全正則替換,更有強悍的引擎使用詞法分析,總之各顯神通。 

全正則替換的方案只是一長串的 .replace() 鏈式調用,看起來代碼更加美妙,但由於存在中間過渡狀態和方法而導致性能不佳。詞法分析更不必說,大量的正則拖慢編譯速度。編譯優化的重點就在,盡量減少中間態,並減少復雜正則表達式的使用。經過實測,split() 截斷分析能減少一部分正則,性能更好。

tppl 一開始使用“模板尾標簽分割”,即:str.split("%>") 的方式,這與 tmpl 的實現方式不謀而合,上面的字符串被分割為6段,然后為每一段使用一次正則替換:

var tpls = ["<h1> <%=title", " </h1>","<% for(var i in content){ ", "<p>第<%=i", "段:<%=content[i]", "</p><%}"];

在后來的性能測試中,發現這種實現方式相比其它引擎,並沒有太大的提升。看來只能另辟蹊徑了。

經過長時間的冥思苦想,終於發現采用“模板頭標簽分割”的方式,能大大減少分割結果的數量,但是需要修改模板標簽:

<h1> [=:title:] </h1>
[: for(var i in content){ :]
  <p>第[=:i:]段:[=:content[i]:]</p>
[:}:]

方括號 [ 與冒號 : 組成的模板標簽相比 <% 能更加清晰的區分html代碼與js邏輯代碼。通過 .split("[:") 將模板分割為3段:

var tpls = ["<h1> [=:title:] </h1>", " for(var i in content){ :]<p>第[=:i:]段:[=:content[i]:]</p>", "}:]"];

如此一來正則替換從6次下降到3次,性能提升將近一倍!而且隨着代碼邏輯結構的不同,性能提升將會更大。

關鍵的正則表達式:

var line = "'"+"<p>第[=:i:]段:[=:content[i]:]</p>".replace(/\[\=\:(.*?)\:\]/g, "'+$1+'")+"'";
// '<p>第'+i+'段:'+content[i]+'</p>'

tppl 的源碼托管在 Github 上,地址:https://github.com/yangjiePro/tppl

如果你還有更好編譯優化方法,歡迎討論!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM