第二章 jQuery技術解密 (四)

mybwu_com發表於2013-12-28

2.3.4 生成 DOM 元素

jQuery.fn.init() 建構函式能夠構建 jQuery 物件,並把匹配的 DOM 元素儲存在 jQuery 物件內部集合中。jQuery.fn.init() 建構函式可以接收單個的 DOM 元素,也可以接收 DOM 集合。如果接收的是字串型 ID 值,則直接在文件中查詢對應的 DOM 元素,並把它傳遞給 jQuery 物件;如果接收的是字串型 HTML 片段,則需要把這個字串片段生成 DOM 元素。下面我們將重點分析 jQuery 是如何把 HTML 片段生成 DOM 元素的。

在2.3.3節中,我們可以看到 jQuery.fn.init() 構造器通過 jQuery.clean([match[1], context]); 語句實現把 HTML 片段生成 DOM 元素,jQuery.clean() 是一個公共函式。原始碼及其註釋如下所示。

jQuery.clean() 包含三個引數,其中 elems 和 context 可以支援多種形式的值。Elems 引數可以為陣列、類陣列、物件結構的形式。陣列元素和物件屬性可以混合使用。

對於數字型別引數,則會被轉換為字串型,除了字串型外,其他的都放入返回的陣列中,當然對於集合形式只需要讀取集合中每個元素即可。

對於字串型引數,則把它轉換成 DOM 元素,再存入返回的陣列中。轉換的方式是,把 HTML 字串片段賦值給建立的 div 元素的 innerHTML ,這樣就可以把 HTML 字串片段掛到 DOM 文件樹中,從而實現把字串轉換成 DOM 元素。

在轉換過程中,應該考慮 HTML 語法約定,因為標籤巢狀是有嚴格限制的,例如,<td> 必須存在 <tr> 中。因此在執行轉換前,還應該對 HTML 字串進行預處理,即修正 HTML 標籤不規範的用法,這也是 jQuery.clean() 函式的一個重要工作。

  1. <scripttype="text/javascript">
  2. (function(){
  3. var
  4. window=this,
  5. jQuery=window.jQuery=window.$=function(selector,context){
  6. returnnewjQuery.fn.init(selector,context);
  7. },
  8. quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/;
  9. jQuery.fn=jQuery.prototype={
  10. init:function(selector,context){
  11. selector=selector||document;
  12. if(typeofselector=="string"){
  13. varmatch=quickExpr.exec(selector);
  14. if(match&&(match[1]||!context)){
  15. //第二種情況,處理HTML字串,類似$(html)->$(array)
  16. if(match[1]){
  17. selector=jQuery.clean([match[1]],context);
  18. }
  19. }
  20. }
  21. }
  22. };
  23. jQuery.fn.init.prototype=jQuery.fn;
  24. //jQuery功能擴充套件函式
  25. jQuery.extend=jQuery.fn.extend=function(obj){
  26. for(varpropinobj){
  27. this[prop]=obj[prop];
  28. }
  29. returnthis;
  30. };
  31. //公共函式擴充套件
  32. jQuery.extend({
  33. //引數說明:object表示jQuery物件,callback表示回撥函式,args回撥函式的引數陣列
  34. each:function(object,callback,args){
  35. varname,i=0,length=object.length;
  36. if(args){//如果存在回撥函式的引數陣列
  37. if(length===undefined){//如果object不是jQuery物件
  38. for(nameinobject){//遍歷object的屬性
  39. if(callback.apply(object[name],args)===false)
  40. //在物件上呼叫回撥函式
  41. break;//如果回撥函式返回值為false,則跳出迴圈
  42. }
  43. }else{
  44. for(;i<length;)//遍歷jQuery物件陣列
  45. if(callback.apply(object[i++],args)===false)
  46. //在物件上呼叫回撥函式
  47. break;//如果回撥函式返回值為false,則跳出迴圈
  48. }
  49. }else{
  50. if(length===undefined){//如果object不是jQuery物件
  51. for(nameinobject)//遍歷object的屬性
  52. if(callback.call(object[name],name,object[name])===false)
  53. break;//如果回撥函式返回值為false,則跳出迴圈
  54. }else{//如果object是jQuery物件
  55. for(varvalue=object[0];//遍歷jQuery物件陣列
  56. i<length&&callback.call(value,i,value)!==false;value=object[++i]){}
  57. }
  58. }
  59. },
  60. //把HTML字串片段轉換成DOM元素
  61. //引數說明:
  62. //elems參數列示多個HTML字串片段的資料
  63. //context參數列示上下文
  64. //fragment參數列示框架物件
  65. clean:function(elems,context,fragment){
  66. context=context||document;//預設的上下文是document
  67. //在IE中!context.createElement是錯誤用法,因為它返回的是物件型別,而不是邏輯值,
  68. //故通過返回型別進行判斷
  69. if(typeofcontext.createElement=="undefined")
  70. //支援context為jQuery物件,並獲取第一個元素
  71. context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;
  72. //如果僅匹配一個標籤,且沒有指定框架引數,則直接建立DOM元素,並跳過後面的解析
  73. if(!fragment&&elems.length===1&&typeofelems[0]==="string"){
  74. varmatch=/^<(\w+)\s*\/?>$/.exec(elems[0]);
  75. if(match)
  76. return[context.createElement(match[1])];
  77. }
  78. varret=[],scripts=[],div=context.createElement("div");
  79. //匹配每一個HTML字串片段,併為每一個片段執行回撥函式
  80. jQuery.each(elems,function(i,elem){
  81. if(typeofelem==="number")//把數值轉換為字串的高效方法
  82. elem+='';
  83. if(!elem)//如果不存在元素,則返回,或者為''、undefined、false等時也返回
  84. return;
  85. //HTML字串轉換為DOM節點
  86. if(typeofelem==="string"){
  87. //統一轉換為XHTML嚴謹型文件的標籤形式,如<div/>的形式修改為<div></div>
  88. //但是對於(abbr|br|col|img|input|link|meta|param|hr|area|embed)不修改
  89. //front=(<(\w+)[^>]*?)--非貪婪的重複
  90. elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){
  91. //all--匹配項
  92. //front--第一個捕獲組
  93. //tag--第二個捕獲組
  94. returntag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?
  95. all:
  96. front+"></"+tag+">";
  97. });
  98. //清除空格,否則indexof可能會出現不能正常工作,elem.replace(/^\s+/,"")清除左側空格
  99. vartags=elem.replace(/^\s+/,"").substring(0,10).toLowerCase();
  100. //有些標籤必須是有一些約束的,如<option>必須在<select></select>中間
  101. //下面程式碼大部分是對<table>中的子元素進行修正。陣列中第一個元素為深度
  102. varwrap=
  103. //約束<option><opt在開始位置(index=0)就返回&&運算子後面的陣列(!0返回true)
  104. !tags.indexOf("<opt")&&
  105. [1,"<selectmultiple='multiple'>","</select>"]||
  106. //<leg必須在<fieldset>內部
  107. !tags.indexOf("<leg")&&
  108. [1,"<fieldset>","</fieldset>"]||
  109. //thead|tbody|tfoot|colg|cap必須在<table>內部
  110. tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&
  111. [1,"<table>","</table>"]||
  112. //tr在<tbody中間
  113. !tags.indexOf("<tr")&&
  114. [2,"<table><tbody>","</tbody></table>"]||
  115. //td在tr中間
  116. (!tags.indexOf("<td")||!tags.indexOf("<th"))&&
  117. [3,"<table><tbody><tr>","</tr></tbody></table>"]||
  118. //col在<colgroup>中間
  119. !tags.indexOf("<col")&&
  120. [2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||
  121. //IE中link,script不能序列化
  122. //IEcan'tserialize<link>and<script>tagsnormally
  123. !jQuery.support.htmlSerialize&&
  124. [1,"div<div>","</div>"]||
  125. //預設不修正
  126. [0,"",""];
  127. //包裹HTML之後,採用innerHTML轉換成DOM
  128. div.innerHTML=wrap[1]+elem+wrap[2];
  129. //轉到正確的深度,對於[1,"<table>","</table>"],div=<table>
  130. while(wrap[0]--)
  131. div=div.lastChild;
  132. //fragments去掉IE對<table>自動插入的<tbody>
  133. //if(!jQuery.support.tbody){
  134. //第一種情況:tags以<table>開頭但沒有<tbody>。在IE生成的元素中可能自動加<tbody>
  135. //第二種情況:thead|tbody|tfoot|colg|cap為tags,那wrap[1]=="<table>"
  136. //TODO
  137. //}
  138. //使用innerHTML,IE會去掉開頭的空格節點,因此加上去掉的空格節點
  139. //TODO
  140. //elem從字元轉換成了陣列
  141. //TODO
  142. }
  143. //如果是DOM元素,則推入陣列,否則就合併陣列
  144. /*if(elem.nodeType)
  145. ret.push(elem);
  146. else
  147. ret=jQuery.merge(ret,elem);*/
  148. });
  149. //如果指定了第3個引數,即框架物件,則附加到框架物件上
  150. //這段是新增加的,用來處理js程式碼,同時也取消了form的處理
  151. if(fragment){
  152. //TODO
  153. }
  154. //返回DOM元素集合
  155. returnret;
  156. }
  157. });
  158. })();
  159. window.onload=function(){
  160. varcontext=document.getElementById("wrap");
  161. //測試程式碼
  162. $("<option/><div/>",context);
  163. };
  164. </script>
上面這段程式碼實際上最後訪問的是一個名為 ret 的陣列,陣列中的元素變為 DOM 元素的物件,而它的 innerHTML 正好就是剛才的 HTML 字串。

相關文章