toDom方法用來將html標簽字符串轉化成DOM節點。1.7之后toDom方法被分配到了dom-construct模塊。
require(["dojo/dom-construct"], function(domConstruct){ // Take a string and turn it into a DOM node var node = domConstruct.toDom("<div>I'm a Node</div>"); });
dom操作是每位想要有所建樹的前端開發者必須要跨過的檻,類庫雖好常用有依賴,對於類庫里常用的函數,我們要做到知其然知其所以然。toDOM將html轉換為dom節點,我能想到的是兩種方法:
- 利用正則表達式,依次匹配出所有標簽;首先需要一個正確的正則,其次需要保證正確的節點關系
- 利用dom的api來做,這個我們可以創建一個元素使用innerHTML來自動轉換
- innerHTML取值時會把所有的子元素作為文本輸出;
- 設值時,會先將字符串轉化為dom節點,然后用dom節點替換元素中的子元素;此時如果字符串中有特殊標簽開頭,比如tbody、thead、tfoot、tr、td、th、caption、colgroup、col等;對於必須存在包裝元素的標簽,瀏覽器不會為這些標簽補全包裝元素,或者統一作為文本處理,或者忽略這些標簽那我們就有必要對html標簽進行一些修正,主要是針對必須存在於包裝元素的標簽;這些標簽作為innerHTML賦值會被瀏覽器忽略,但是如果作為dom節點直接掛載到dom樹中,瀏覽器會為他們自動創建隱含的包裝元素。所以在遇到這些標簽開頭的html片段時,我們需要手動補全缺失的包裝元素。
下面我們來看一下dom-construct模塊是怎么處理的。
找出所有待補全的元素:tbody、thead、tfoot、tr、td、th、caption、colgroup、col、legend、li;dojo中使用如下結構將某些缺失的標簽管理起來:
var tagWrap = { option: ["select"], tbody: ["table"], thead: ["table"], tfoot: ["table"], tr: ["table", "tbody"], td: ["table", "tbody", "tr"], th: ["table", "thead", "tr"], legend: ["fieldset"], caption: ["table"], colgroup: ["table"], col: ["table", "colgroup"], li: ["ul"] },
經過下面這一步處理后,tagWrap中的每一項中多了兩個屬性, eg:tagWrap.tr.pre = "<table><tbody>"和tagWrap.tr.post = "</tbody></table>";
for(var param in tagWrap){ if(tagWrap.hasOwnProperty(param)){ var tw = tagWrap[param]; tw.pre = param == "option" ? '<select multiple="multiple">' : "<" + tw.join("><") + ">"; tw.post = "</" + tw.reverse().join("></") + ">"; } }
1、innerHTML方式需要一個額外的元素,作為臨時的容器,所以利用一下變量來管理這個額外的元素:
var reTag = /<\s*([\w\:]+)/,//用來判斷字符串參數中是否含有html標簽 masterNode = {},//作為倉庫來管理臨時容器 masterNum = 0,//z這兩個變量用來標識臨時容器 masterName = "__" + dojo._scopeName + "ToDomId";
2、toDom方法中,首先創建一個臨時容器,是一個div元素:
doc = doc || win.doc; var masterId = doc[masterName]; if(!masterId){ doc[masterName] = masterId = ++masterNum + ""; masterNode[masterId] = doc.createElement("div"); }
3、然后判斷frag中是否含有html標簽,如果含有html標簽而且需要我們補全包裝元素,則利用上面生成的pre和post補全標簽后傳遞給master這個容器的innerHTML,這一步完成后找到我們傳入的html標簽對應的dom樹,賦值給master;如果不需要包裝,直接賦值給master.innerHTML
var match = frag.match(reTag), tag = match ? match[1].toLowerCase() : "", master = masterNode[masterId], wrap, i, fc, df; if(match && tagWrap[tag]){ wrap = tagWrap[tag]; master.innerHTML = wrap.pre + frag + wrap.post; for(i = wrap.length; i; --i){ master = master.firstChild; } }else{ master.innerHTML = frag; }
這里僅是簡單的認為如果正則匹配則進行包裝處理,按照我的理解,正則的寫法應該為:/^<\s*([\w\:]+)/,原因看下面例子:
第一個表達式子所以報錯,就是因為“adffd”這部分在dom中被作為文本節點,文本節點並沒有子節點。更改了正則之后,如果不是html標簽做開頭則統一作為文本節點添加到dom中去。
4、將html標簽轉化成dom后,如果僅有一個元素則返回這個元素,否則將轉化后的元素,放入到文檔片段中。
if(master.childNodes.length == 1){ return master.removeChild(master.firstChild); // DOMNode } df = doc.createDocumentFragment(); while((fc = master.firstChild)){ // intentional assignment df.appendChild(fc); } return df; // DocumentFragment
參考標准的描述,DocumentFragment是一個輕量級的文檔對象,能夠提取部分文檔的樹或創建一個新的文檔片段。可以通過appendChild()或insertBefore()將文檔片段中內容添加到文檔中。在將文檔片段作為參數傳遞給這兩個方法時,實際上只會將文檔片段的所有子節點添加到相應的位置上;文檔片段本身永遠不會稱為文檔樹的一部分
利用innerHTML標簽創建dom元素,並自動補齊缺失的標簽,這就是dom-construct模塊針對toDOM方法的實現思路。

1 exports.toDom = function toDom(frag, doc){ 2 // summary: 3 // instantiates an HTML fragment returning the corresponding DOM. 4 // frag: String 5 // the HTML fragment 6 // doc: DocumentNode? 7 // optional document to use when creating DOM nodes, defaults to 8 // dojo/_base/window.doc if not specified. 9 // returns: 10 // Document fragment, unless it's a single node in which case it returns the node itself 11 // example: 12 // Create a table row: 13 // | require(["dojo/dom-construct"], function(domConstruct){ 14 // | var tr = domConstruct.toDom("<tr><td>First!</td></tr>"); 15 // | }); 16 17 doc = doc || win.doc; 18 var masterId = doc[masterName]; 19 if(!masterId){ 20 doc[masterName] = masterId = ++masterNum + ""; 21 masterNode[masterId] = doc.createElement("div"); 22 } 23 24 if(has("ie") <= 8){ 25 if(!doc.__dojo_html5_tested && doc.body){ 26 html5domfix(doc); 27 } 28 } 29 30 // make sure the frag is a string. 31 frag += ""; 32 33 // find the starting tag, and get node wrapper 34 var match = frag.match(reTag), 35 tag = match ? match[1].toLowerCase() : "", 36 master = masterNode[masterId], 37 wrap, i, fc, df; 38 if(match && tagWrap[tag]){ 39 wrap = tagWrap[tag]; 40 master.innerHTML = wrap.pre + frag + wrap.post; 41 for(i = wrap.length; i; --i){ 42 master = master.firstChild; 43 } 44 }else{ 45 master.innerHTML = frag; 46 } 47 48 // one node shortcut => return the node itself 49 if(master.childNodes.length == 1){ 50 return master.removeChild(master.firstChild); // DOMNode 51 } 52 53 // return multiple nodes as a document fragment 54 df = doc.createDocumentFragment(); 55 while((fc = master.firstChild)){ // intentional assignment 56 df.appendChild(fc); 57 } 58 return df; // DocumentFragment 59 };