前端模板中,我們通常使用 script/textarea 來存放模板代碼,然后使用 innerHTML/value 屬性來獲取模板內容進行解析和拼裝。
<script type="text/x-template" id="tpl"> <h1><%=data.title%></h1> <p><%=data.content%></p> </script> <script> var htmlTpl = document.getElementById("tpl").innerHTML; tplEngine(htmlTpl, { title: "This is title", content: "This is content" }); </script>
關於 tplEngine 這個 Javascript 模板引擎,之前也寫了篇 文章 介紹過,這里就不贅述了。除了使用 script 標簽,textarea 也可以達到同樣的效果,但是本文敘述的重點並不是如何去解析一個 JavaScript 模板。
W3C工作組在 HTML 中加入了一個新的標簽 ——TEMPLATE。他提供了一個可以定義 HTML 代碼片段的機制,下面就來詳細說說這個 TEMPLATE 標簽。
本文地址:http://www.cnblogs.com/hustskyking/p/javascript-template-tag.html,轉載請注明源地址。
一、先看 DEMO
運行下面的 demo,或許你已經知道了一些東西了。
<ul id="list"> <!-- TEMPLATE 模板 --> <template id="tpl"> <li><span></span> - <span></span></li> </template> </ul> <button id="btn">見證奇跡的時刻→</button> <script> var datas = [ {name:"李靖", age:"21"}, {name:"Barret Lee", age:"21"} ]; btn.onclick = function(){ for(var i = 0, len = datas.length; i < len; i++){ var data = datas[i]; // 獲取模板代碼 var htmlTpl = tpl.content.cloneNode(true); // 插入數據 var spans = htmlTpl.querySelectorAll("span"); spans[0].textContent = data.name; spans[1].textContent = data.age; // 插入到 DOM 中 list.appendChild(htmlTpl); } }; </script>
這里使用的 template 標簽,標簽的內容沒有被解析,我們並沒有也使用 innerHTML 這種暴力手段獲取模板內容。
二、template 標簽特性
- 這個標簽可以被定義在任何位置:head 中、body中、甚至是一個 frame 中。
- 標簽內容不會顯示出來
- Template 標簽不被當做 document 的一部分,你可以去試試彈出
document.getElementById("tpl").length
, 或者看看他其他的屬性,得到的結果都是 undefined。 - 標簽內容在被應用之前,都是 inactive 狀態,也就是說模板中的 img、audio、script 標簽都不會執行(加載)
三、瀏覽器對 template 標簽的解析
每一個 template 元素都會和一個 DocumentFragment 對象關聯,當一個 template 元素被創建時,瀏覽器會運行如下操作:
- 讓文檔(doc)是模板元素的ownerDocument的相應的模板內容擁有者文檔(owerDocument)。
- 創建一個 DocumentFragment 對象,這個對象的擁有者文檔(owerDocument)為 doc
- 將模板文檔的 content 內容放到上述新創建的 DocumentFragment 中
上面的過程我是翻譯 w3c 的規范文檔,讀起來相當晦澀,如果你了解 shadowDOM,那理解起來就輕松了,template 在解析是,其內容被解析成一個 shadowDOM,我們只能使用 content 屬性來獲取到這個 shadowDOM 的內容。
四、兼容性與需要注意的地方
很可惜,這玩意兒雖然好用,但 IE 目前還不支持,當然 Chrome 32+ | Firefox 25+ 都提供了支持。
1. 克隆節點而不是直接使用
從上面的 demo 中,可以發現,獲取 template 標簽的內容,其方式是:
document.getElementById("tpl").content
但是我並不是直接將 content 賦值給 htmlTpl,而是:
htmlTpl = tpl.content.cloneNode(true);
為什么要這么做呢?如果你不是用 cloneNode,而是直接將內容 appendChild 到 DOM 樹中,documentFragment 內的內容就會被清空,上面我們說了 template 標簽內容就是一個 documentFragment 的 shadowDOM,所以應該使用 cloneNode 或者 importNode 方法將內容復制到 DOM 中,這樣才能保證這個 shadowDOM 內容不被清空,從而可以復用(你可以把上面 demo 的 cloneNode 函數去掉,看看結果如何)。
2. 不支持 template 標簽的降級處理
其實也沒有比較好的降級處理方案,如果你在 template 中放了 script 或者 img 節點,這些內容都會被解析出來,你阻止不了,所以如果你的程序要兼容所有的瀏覽器,暫時就不要用了。當然,你可以做這樣的判斷:
if (!"content" in document.createElement("template")){ // code here.. return; }
3. 模板中嵌入模板
在 script 標簽中嵌入一個 script 標簽,這個幾乎是不可能的事情吧,但是 template 可以:
<template id="ulList"> <li> <strong><%=content%></strong> <template> <div> <p><%=detail%></p> </div> </template> </li> </template>
至於插入之后是個什么效果,讀者可以自己去瀏覽器中查看。這種插入方式是有使用場景的,很多時候我們都是給需要應用模板的元素設置一個 id 或者 class ,方便找到他們,而這種直接插入的方式,我們可以利用模板代碼直接找到需要應用模板的元素,如:
var tpl = ulList.getElementsByTagName("template")[0]; // 獲取模板 var toBox = tpl.parentNode; // 直接定位要插入的位置 toBox.appendChild(tpl.content.cloneNode(true)); // 插入
五、拓展 web components
Web Components 是一些規范,旨在以瀏覽器原生的方式向外提供組件,它的規范如下:
- 模板(Templates)可以將不必立即渲染的元素,不必立即執行的腳本放入這里。
- 裝飾器(Decorators)
- Shadow DOM
- 自定義元素(Custom Elements),實現自定義html標簽,及屬性。擁有同原生組件一樣的生命周期
- Imports, 指定引入的組件文檔及類型
其實本文提到的內容就是 web components 的冰山一角,感興趣的童鞋可以去讀一讀相關的內容。
- http://www.html5rocks.com/zh/tutorials/webcomponents/imports/ HTML Imports
- http://www.w3.org/TR/components-intro/ w3c components
- http://www.html5rocks.com/zh/search?q=web+components search
六、小結
本文稀里嘩啦說了一大串,主要是簡單介紹 web components 中的 template 標簽,用以替換模板代碼容器 script/textarea,web components 肯定是 web 發展的一個大頭,尤其是移動開發上,很有必要深入研究。
七、參考資料
- http://www.whatwg.org/specs/web-apps/current-work/multipage//scripting-1.html#the-template-element W3C
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template MDN
- http://www.c-sharpcorner.com/UploadFile/370e35/template-tag-in-html5/ Sunny Kumar