innerHTML 這個由 IE 引入的屬性成了事實標准,各瀏覽器均支持。盡管html4中沒有承認它,但html5已經正式將其納入 。
我們知道任何一個庫都少不了DOM操作,因為用JS操作DOM(早期微軟稱DHTML)是日常開發中最基本的工作之一。
這篇主要講述Ext.DomHelper中的 createHtml 函數。首先Ext.DomHelper為一個單例對象。使用其時可沿用Ext庫的習慣使用別名dh
var dh = Ext.DomHelper; // 使用dh別名
dh有以下方法:
markup
applyStyles
insertHtml
insertBefore
insertAfter
insertFirst
append
overwrite
createHtml
dh的createHtml方法就是整個閉包中私有的createHtml。
dh的markup方法內部調用的就是整個閉包中私有的createHtml。
dh的overwrite方法內部也用到了createHtml。
此外私有的doInsert函數內部用用到了createHtml,而dh的insertBefore、insertAfter、insertFirst、append方法用到了doInsert。
因此可以看到私有的createHtml函數是dh中處在底層的,最重要的函數 。它們之間的關系如下圖
createHtml 的定義如下
function createHtml(o){ var b = '', attr, val, key, keyVal, cn; if(Ext.isString(o)){ b = o; } else if (Ext.isArray(o)) { for (var i=0; i < o.length; i++) { if(o[i]) { b += createHtml(o[i]); } }; } else { b += '<' + (o.tag = o.tag || 'div'); Ext.iterate(o, function(attr, val){ if(!/tag|children|cn|html$/i.test(attr)){ if (Ext.isObject(val)) { b += ' ' + attr + '="'; Ext.iterate(val, function(key, keyVal){ b += key + ':' + keyVal + ';'; }); b += '"'; }else{ b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"'; } } }); // Now either just close the tag or try to add children and close the tag. if (emptyTags.test(o.tag)) { b += '/>'; } else { b += '>'; if ((cn = o.children || o.cn)) { b += createHtml(cn); } else if(o.html){ b += o.html; } b += '</' + o.tag + '>'; } } return b; }
雖然代碼較多,但接口卻很簡單:傳入了一個參數o,返回了一個字符串b。
createHtml 內部有三個分支
分支1 ,參數o為字符串時直接返回
分支2 ,參數o為數組時遞歸調用自身.Ext.isArray用來判斷所傳參數是否為一個數組類型,該方法在 讀Ext之二(實用方法) 中提到。
分支3 ,參數o為對象時(通常使用最多的情況)
分之一的情況很簡單
var str = createHtml('<div>test</div>'); alert(str); // "<div>test</div>"
分支二的情況
var str = createHtml(['<div>test</div>','<p>pp</p>']); alert(str); // "<div>test</div><p>pp</p>"
分支三的情況
var obj = { tag:'ul', children:[{tag:'li',html:'li 1'},{tag:'li',html:'li 2'}] }; var str = createHtml(obj); alert(str); // "<ul><li>li 1</li><li>li 2</li></ul>"
分支三的詳細情況如下
b += '<' + (o.tag = o.tag || 'div');
b為空字符串,該語句執行完為:"<tag",如果沒有所傳對象沒有tag屬性,默認創建div即"<div"
接下來是為根元素添加屬性,
Ext.iterate(o, function(attr, val){ if(!/tag|children|cn|html$/i.test(attr)){ if (Ext.isObject(val)) { b += ' ' + attr + '="'; Ext.iterate(val, function(key, keyVal){ b += key + ':' + keyVal + ';'; }); b += '"'; }else{ b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"'; } } });
Ext.iterate方法是一個通用迭代器,可以迭代數組,也可以是對象。在 讀Ext之二 提到。即所傳對象o的屬性不為tag、children、cn、html時給該元素添加html屬性
var obj = { tag: 'input', name: 'uname', cls: 'myClass', id: 'myId' }; var str = createHtml(obj); // 創建input元素,添加name、class及id屬性 alert(str); // <input name="uname" class="myClass" id="myId"/>
對html元素的class,for屬性做了特殊處理,因為js中class是保留字,for是關鍵字。
此外有些屬性值是復合值組成,如style
var obj = { tag: 'p', style: {width:'100px',height:'100px',background:'red'} }; var str = createHtml(obj); alert(str); // <p style="width:100px;height:100px;background:red;"></p>
接下來
if (emptyTags.test(o.tag)) { b += '/>'; }
emptyTags 是一個正則,這句是對非閉合標簽的處理,非閉合標簽如br、input、img等。
所有的非閉合標簽,都會以"/>"結束,這里遵循xml的習慣。對於非閉合標簽不會有html內容。
接下來
else { b += '>'; if ((cn = o.children || o.cn)) { b += createHtml(cn); } else if(o.html){ b += o.html; } b += '</' + o.tag + '>'; }
對於閉合標簽,先完成首標簽閉合。拿div示例,由先"<div"變成了"<div>"。
接下來如果有children或cn屬性,則添加子元素,是一個遞歸調用,即子元素如果有屬性,子元素仍然會繼續遞歸添加。
最后是結束標簽"</div>"
這就是這個createHtml函數了。該函數實現的很緊湊,巧妙。
createHtml最終返回的是html片段,該片段會通過innerHTML添加到文檔中去。
上面的關系圖已經可以看到:
markup方法直接調用了createHTML
markup : function(o){ return createHtml(o); },
個人認為這個方法和markup方法重復了。
doInsert方法也直接調用了createHtml
function doInsert(el, o, returnElement, pos, sibling, append){ var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o)); return returnElement ? Ext.get(newNode, true) : newNode; }
好了,下一篇會接着看DomHelper的其它方法。