buildFragment是一个私有函数,只有在DomManip和jQuery.parseHTML中使用。 此函数创建一个文档片段fragment, 然后将HTML代码转换为DOM元素,并储存在创建的文档片段中。
文档片段fragment表示文档的一部分,但不属于文档树。当把fragment插入文档树时,插入的不是fragment自身,而是它的所有子孙节点,即可以一次向文档树中插入多个节点。当需要插入大量节点时,相比于逐个插入节点,使用fragment一次插入多个节点,性能的提升会非常明显。
执行的关键步骤:
1) 用content创建文档对象fragment
2) 用elems转换成DOM元素存放于数组nodes中
3) 将nodes元素循环放入到fragment中
4) 返回fragment
// 涉及函数
// 代码行:4672——4738
var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );
var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
// We have to close these tags to support XHTML (#13200)
// 对象wrapMap中包含了以下标签:option、thead、tr、td、th、col、_default、optgroup、tbody、tfoot、colgroup、caption,每个标签对应一个数组,数组中的元素依次是:包裹的深度、包裹的父标签、父标签对应的关闭标签。例如:thead对应的父标签是table,包裹的深度为1。XHTML语法要求这些标签必须包含在其对应的父标签中,在随后设置临时div元素的innerHTML属性之前,会在标签前后自动加上父标签和关闭标签。
var wrapMap = {
// Support: IE <=9 only
option: [ 1, "<select multiple='multiple'>", "</select>" ],
// XHTML parsers do not magically insert elements in the
// same way that tag soup parsers do. So we cannot shorten
// this by omitting <tbody> or other required elements.
thead: [ 1, "<table>", "</table>" ],
col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
_default: [ 0, "", "" ]
};
// Support: IE <=9 only
wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;
// 获取在指定内容中所有查找的元素节点
function getAll( context, tag ) {
// Support: IE <=9 - 11 only
// Use typeof to avoid zero-argument method invocation on host objects (#15151)
var ret;
// 数组,用于存放在context中查找的节点
// 如果传入的context存在getElementsByTagName方法,则使用getElementsByTagName()查找节点列表
if ( typeof context.getElementsByTagName !== "undefined" ) {
ret = context.getElementsByTagName( tag || "*" );
// etElementsByTagName() 方法可返回带有指定标签名的对象的集合。
// 如果把特殊字符串 "*" 传递给 getElementsByTagName() 方法,它将返回文档中所有元素的列表,元素排列的顺序就是它们在文档中的顺序。
// 如果传入的context存在querySelectorAll方法,则使用querySelectorAll()查找节点列表
} else if ( typeof context.querySelectorAll !== "undefined" ) {
ret = context.querySelectorAll( tag || "*" );
// querySelectorAll()方法返回文档中与指定的CSS选择器匹配的所有元素,作为静态NodeList对象。
// *含义如上。
// 否则返回空数组
} else {
ret = [];
}
// 如果tag为空或tag与context是同一个标签,则把context标签及其子元素节点全部返回
if ( tag === undefined || tag && nodeName( context, tag ) ) {
return jQuery.merge( [ context ], ret );
}
// 返回查找的列表
return ret;
}
// Mark scripts as having already been evaluated
// 将脚本标记为已经被评估
function setGlobalEval( elems, refElements ) {
var i = 0,
l = elems.length;
for ( ; i < l; i++ ) {
// 调用数据缓存模块,对每一个元素设置一个唯一的ID
dataPriv.set(
elems[ i ],
"globalEval",
!refElements || dataPriv.get( refElements[ i ], "globalEval" )
);
}
}
// 主要函数
// 代码行:4741——4830
var rhtml = /<|&#?\w+;/;
// 用于检测HTML代码中是否含有标签、字符代码或数字代码。标签的特征字符是左尖括号“<”,字符代码的特征字符是“&”,数字代码的特征字符是“&#”。字符代码和数字代码是特殊符号的两种形式。
function buildFragment(elems, context, scripts, selection, ignored) {
// elems:数组,含有待转换成DOM的HTML元素。
// context:数组,含有文档对象、jQuery对象或DOM元素,用于修正创建文档片段fragment
// scripts: 数组,用于存放HTML代码中的script元素。
//selection: 数组,已经被选中的元素节点列表
//ignored: 数组,用来存放需要替换的内容
// 定义局部变量,并创建一个虚拟的对象
var elem,
// 存放elems中单个元素
tmp,
//一个含有div元素的虚拟DOM元素
tag,
// 存放HTML的标签
wrap,
// HTML标签的父标签
contains,
// 判断判断elem的DOM节点是否包含elem
j,
fragment = context.createDocumentFragment(),
// 变量fragment指向稍后可能创建的文档片段DocumentFragment;
nodes = [],
// nodes用于存放节点
i = 0,
l = elems.length;
// 循环判断elems数组中每一项元素的类型
for (; i < l; i++) {
elem = elems[i];
if (elem || elem === 0) {
// Add nodes directly
// elem是DOM对象
if (toType(elem) === "object") {
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
// 直接合并到nodes数组中
jQuery.merge(nodes, elem.nodeType ? [elem] : elem);
// Convert non-html into a text node
// elem不包含HTML标签、字符代码和数字代码
} else if (!rhtml.test(elem)) {
// 调用原生方法.createTextNode()生成一个成文本节点添加到nodes数组中
// context.createTextNode(elem)用于创建文本节点,但是对于传给它的字符串参数不会做转义解析,也就是说,该方法不能正确的解析和创建包含了字符代码或数字代码的字符串,而浏览器的innerHTML机制则可以。
nodes.push(context.createTextNode(elem));
// Convert html into DOM nodes
// elem是HTML标签
} else {
// 建一个临时div,插入到虚拟节点fragment中,并赋值给tmp。
tmp = tmp || fragment.appendChild(context.createElement("div"));
// Deserialize a standard representation
// 提取HTML代码中的标签部分,删除了前导空白符和左尖括号,并转换为小写赋值给变量tag。
tag = (rtagName.exec(elem) || ["", ""])[1].toLowerCase();
// 从对象wrapMap中取出标签tag对应的父标签并赋值给wrap。
wrap = wrapMap[tag] || wrapMap._default;
// 先为HTML代码包裹必要的父标签,然后赋值给tmp的innerHTML属性,从而将HTML代码转换成DOM元素。
tmp.innerHTML = wrap[1] + jQuery.htmlPrefilter(elem) + wrap[2];
// Descend through wrappers to the right content
// 取出被包裹的深度赋值给变量j,稍后将依据该变量层层剥去包裹的父元素,得到转换后的DOM元素,并赋值给tmp,最终tmp将指向HTML对应的DOM元素的父元素。
j = wrap[0];
while (j--) {
tmp = tmp.lastChild;
}
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
// 合并tmp子节点到nodes数组。
jQuery.merge(nodes, tmp.childNodes);
// Remember the top-level container
// 设置tmp为虚拟节点的第一个子元素
tmp = fragment.firstChild;
// Ensure the created nodes are orphaned (#12392)
// 设置tmp标签的文本内容为空。
tmp.textContent = "";
}
}
}
// Remove wrapper from fragment
// 移除fragment的文本内容
fragment.textContent = "";
// 传入文档片段的判断
i = 0;
// 判断nodes是否存在,存在进行循环且赋值给eleme
while ((elem = nodes[i++])) {
// Skip elements already in the context collection (trac-4087)
// 跳过上下文集合中已经存在的元素
// 判断selection存在且elem在selection中
if (selection && jQuery.inArray(elem, selection) > -1) {
// 如果ignored存在,将elem添加到ignored中
if (ignored) {
ignored.push(elem);
}
continue;
}
// 判断elem的DOM节点是否包含elem,并把结果赋值给contains。
// ownerDocument 属性以 Document 对象的形式返回节点的 owner document。在 HTML 中,HTML 文档本身始终是元素的 ownerDocument。
contains = jQuery.contains(elem.ownerDocument, elem);
// Append to fragment
// 给getAll()传入虚拟节点fragment与script返回一个新对象,并赋值给tmp。
tmp = getAll(fragment.appendChild(elem), "script");
// Preserve script evaluation history
// 如果contains存在
if (contains) {
// 使用setGlobalEval将tmp标记成已经被标记
setGlobalEval(tmp);
}
// Capture executables
// 如果script存在
if (scripts) {
j = 0;
while ((elem = tmp[j++])) {
// 判断script的类型,获取可执行文件,并将elem添加到script中
// 如果一个script元素没有指定属性type,或者属性type的值含有'/JavaScript'或'/ecmascript',则认为是可执行的。
if (rscriptType.test(elem.type || "")) {
scripts.push(elem);
}
}
}
}
// 返回虚拟节点
return fragment;
}