2-6 核心buildFragment
本节编程练习不计算学习进度,请电脑登录imooc.com操作

核心buildFragment

DOM原生的接口是即简单又单一,参数类型确定,也不会重载,每次只会处理一个元素。在看jQuery完全反其道而行之参数复杂多样,接口重载厉害。如果一次传递N多的节点元素那么在处理上要优化就必须引入文档碎片了。

我们知道用文档碎片无非就是先创建:

fragment = context.createDocumentFragment()

然后把所有需要处理的dom节点给appendChild进去:

buildFragment对于文档碎片的创建,可以看到被切分了2个部分:

先看第一部分代码,收集节点元素:

var $newdiv1 = $('<div id="object1"/>'),
    newdiv2 = document.createElement('div'),
    existingdiv1 = document.getElementById('foo');
$('body').append($newdiv1, [newdiv2, existingdiv1,'<td>慕课网</td>','文本','<script>alert(1)'])

这段代码包含了六种不同的类型的参数,基本覆盖了所有了buildFragment的处理其实很简单,我们只需要把不同类型的参数分解后,压入到文档碎片就可以了,当然因为类型的不同处理的方式也有不同。

比如常见的几个问题:

IE对字符串进行trimLeft操作,其余是用户输入处理, 很多标签不能单独作为DIV的子元素, td、th、tr、tfoot、tbody等等,需要加头尾:

<td>慕课网</td>

jQuery通过wrapMap转化成,否则有些会当成普通文本来解释:

"<table><tbody><tr><td>慕课网</td></tr></tbody></table>"

我们参考右边的代码,整个流程如下:

  1.  分解类型,jQuery对象,节点对象,文本,字符串,脚本
  2.  引入nodes收集各种分解的类型数据
  3.  针对html节点,兼容IE的处理,先过滤空白,然后补全tr,td等
  4.  创建文档碎片的div包含节点,把html结构给innerHTML进去
  5.  取出创建的节点,jQuery.merge(nodes, tmp.childNodes),因为靠div包装过

任务

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
  5. <script src="http://code.jquery.com/jquery-latest.js"></script>
  6. <title>domManip</title>
  7. </head>
  8. <body>
  9. <div id='foo'>Hello</div>
  10. <button id="test1">jquery.buildFragment生成文档碎片</button>
  11. <button id="test2">模拟jquery.buildFragment生成文档碎片</button>
  12.  
  13. <script type="text/javascript">
  14.  
  15. var $newdiv1 = $('<div id="object1"/>'),
  16. newdiv2 = document.createElement('div'),
  17. existingdiv1 = document.getElementById('foo');
  18.  
  19.  
  20. var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;
  21. var wrapMap = {
  22. // Support: IE 9
  23. option: [1, "<select multiple='multiple'>", "</select>"],
  24. thead: [1, "<table>", "</table>"],
  25. col: [2, "<table><colgroup>", "</colgroup></table>"],
  26. tr: [2, "<table><tbody>", "</tbody></table>"],
  27. td: [3, "<table><tbody><tr>", "</tr></tbody></table>"],
  28. _default: [0, "", ""]
  29. };
  30. // Support: IE 9
  31. wrapMap.optgroup = wrapMap.option;
  32. wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
  33. wrapMap.th = wrapMap.td;
  34.  
  35.  
  36. function buildFragment(elems, context) {
  37. var elem, tmp, tag, wrap, contains, j,
  38. fragment = context.createDocumentFragment(),
  39. nodes = [],
  40. i = 0,
  41. l = elems.length;
  42.  
  43. //筛选出不同类型的节点
  44. for (; i < l; i++) {
  45. elem = elems[i];
  46.  
  47. if (elem || elem === 0) {
  48. if (jQuery.type(elem) === "object") {
  49. // 如果是jQuery对象
  50. // 如果是普通元素对象加[elem]
  51. // 取出ele放入nodes数组中
  52. jQuery.merge(nodes, elem.nodeType ? [elem] : elem);
  53. // 没有html结构,是一个文本节点
  54. } else if (!/<|&#?\w+;/.test(elem)) {
  55. nodes.push(context.createTextNode(elem));
  56. } else {
  57. //创一个元素div做为容器
  58. tmp = tmp || fragment.appendChild(context.createElement("div"));
  59. tag = (/<([\w:]+)/.exec(elem) || ["", ""])[1].toLowerCase();
  60. //ie对字符串进行trimLeft操作,其余是用户输入处理
  61. //很多标签不能单独作为DIV的子元素
  62. //td,th,tr,tfoot,tbody等等,需要加头尾
  63. wrap = wrapMap[tag] || wrapMap._default;
  64. tmp.innerHTML = wrap[1] + elem.replace(rxhtmlTag, "<$1></$2>") + wrap[2];
  65.  
  66. // Descend through wrappers to the right content
  67. // 因为warp被包装过
  68. // 需要找到正确的元素父级
  69. j = wrap[0];
  70. while (j--) {
  71. tmp = tmp.lastChild;
  72. }
  73. // Support: QtWebKit
  74. // jQuery.merge because push.apply(_, arraylike) throws
  75. // 把节点拷贝到nodes数组中去
  76. jQuery.merge(nodes, tmp.childNodes);
  77. }
  78. }
  79. }
  80. i = 0;
  81. while ((elem = nodes[i++])) {
  82. fragment.appendChild(elem)
  83. }
  84. return fragment;
  85. }
  86.  
  87.  
  88.  
  89. $('#test1').click(function() {
  90. "<table><tbody><tr><td>慕课网</td></tr></tbody></table>"
  91. $('body').append($newdiv1, [newdiv2, existingdiv1, '<td>慕课网</td>', '文本', '<script>alert(1)'])
  92. })
  93.  
  94. $('#test2').click(function() {
  95. //6中类型的数据结构
  96. //这里不设计script的执行
  97. var fragment = buildFragment([$newdiv1, newdiv2, existingdiv1, '<td>慕课网</td>', '文本', '<script>alert(1)'], document)
  98. document.body.appendChild(fragment)
  99. })
  100.  
  101.  
  102.  
  103. </script>
  104. </body>
  105. </html>
下一节