二、构造jQuery对象2——buildFragment( elems, context, scripts, selection, ignored )

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;
}

 

posted @ 2019-03-29 11:00  道鼎金刚  阅读(272)  评论(0)    收藏  举报