模闆的誕生是為了将顯示與資料分離,模闆技術多種多樣,但其本質是将模闆檔案和資料通過模闆引擎生成最終的html代碼。

模闆技術并不是什麼神秘技術,幹的是拼接字元串的體力活。模闆引擎就是利用正規表達式識别模闆辨別,并利用資料替換其中的辨別符。比如:
資料是<code>{name: '木的樹'}</code>,那麼通過模闆引擎解析後,我們希望得到<code>hello, 木的樹</code>。模闆的前半部分是普通字元串,後半部分是模闆辨別,我們需要将其中的辨別符替換為表達式。模闆的渲染過程如下:
上面我們示範是簡單的字元串替換,但對于模闆引擎來說,要做的事情更複雜些。通常需要以下幾個步驟:
利用正規表達式分解出普通字元串和模闆辨別符,<code><%=%></code>的正規表達式為<code>/<%=\s*([^%>]+)\s*%>/g</code>.
将模闆辨別符轉換成普通的語言表達式
生成待執行語句
将資料填入執行,生成最終的字元串
demo代碼如下:
上述代碼中有如下部分:
為了能夠與資料一起執行生成字元串,我們需要将原始的模闆字元串轉換成一個函數對象。這個過程稱為<code>模闆編譯</code>。模闆編譯使用了<code>new function()</code>, 這裡通過它建立了一個函數對象,文法如下:
<code>function()</code>構造函數接受多個參數,最後一個參數作為函數體的内容,其之前的參數全部作為生成的新函數的參數。需要注意的是function的參數全部是字元串類型,函數體部分對于字元串跟函數表達式一定要區厘清楚,初學者往往在對函數體字元串中的普通字元串和表達式的拼接上犯錯。一定要将函數體字元串和内部字元串正确拼接,如:
或者對其中的字元換使用<code>\"</code>
模闆編譯過程中每次都要利用function重新生成一個函數,浪費cpu。為此我們可以将函數緩存起來,代碼如下:
利用<code>with</code>我們可以不用把模闆辨別符轉換成<code>obj.name</code>,隻需要保持<code>name</code>辨別符即可。
如果上面的obj變成<code>var obj = {name: "<script>alert(\"xss\")</script>"};</code>,那麼最終生成的結果就會變成:
為此我們需要堵上這個漏洞,基本就是要将形成html标簽的字元轉換成安全的字元,這些字元通常是<code>&</code>, <code><</code>, <code>></code>, <code>"</code>, <code>'</code>。轉換函數如下:
這樣下來,模闆引擎應該變成這樣:
這時候我們得到如下結果:
功能稍微強大的模闆引擎,都允許在模闆中添加一部分邏輯來控制頁面的最終渲染。如:
這裡我們用<code><%%></code>代表邏輯代碼<code><%=%></code>代表模闆中需要替換的辨別符。我們的模闆代碼變成了如下所示:
第一步,我們将模闆中的邏輯表達式找出來,用的正規表達式是<code>/<%\s*([^=][^%>]*)\s*%>/g</code>
注意在拼接時,為了防止函數字元串中的字元串沒有閉合對表達式造成影響,我們在<code>key</code>前後都加了<code>'</code>來<code>保證其中的字元串閉合</code>。
第二步, 對可能存在的html标簽進行轉義
同樣需要<code>注意前後的字元串閉合</code>
第三步,像先前一樣處理模闆辨別符
仍然要<code>注意其中的字元串閉合問題</code>。
模闆引擎是一個系統的問題,複雜模闆還支援<code>模闆嵌套</code>,這裡就不介紹了,希望此文能夠抛磚引玉,讓大火帶來更好的幹貨!