【笔记】jQuery源码(文档处理3)

一览

今天是第四天啦!笔记的内容主要是跟着慕课网上的jQuery源码解析系列课程以及自己的理解+实践来写的(也比较偏向于自己的梳理,所以可能会有点乱),可能会有错误,欢迎指出。

jQuery对于DOM操作常用的方法大概有以下几种:

  • append
  • prepend
  • before
  • after
  • replaceWith
  • appendTo
  • prependTo
  • insertBefore
  • insertAfter
  • replaceAll

其中这里有些方法是对应的,但是它们的目标元素和被瞄准元素位置不一样。
对于DOM的移除有以下四种常用的方法:

  • detach
  • empty
  • remove
  • unwrap

插入篇

insertAfter()

比如after()insertAfter()方法:

$('.inner').after('<p>Test</p>');
$('<p>Test</p>').insertAfter('.inner');

这部分课程讲的不是特别清楚,所以直接搬出源码来看:

jQuery.each( 
//可以看到其实insertAfter和After其实本质是一样的
{
    appendTo: "append",
    prependTo: "prepend",
    insertBefore: "before",
    insertAfter: "after",
    replaceAll: "replaceWith"
}, function( name, original ) {
    jQuery.fn[ name ] = function( selector ) {
        var elems,
            ret = [],
            //转为选择器
            insert = jQuery( selector ),
            last = insert.length - 1,
            i = 0;
        
        //给每个选择器去添加对应的元素
        for ( ; i <= last; i++ ) {
            //如果不是最后一次操作,就把元素进行克隆,因为是p调用了这个方法,所以这里的this是加入的p的jQuery对象
            elems = i === last ? this : this.clone( true );
            /* 再进行对应方法操作
            * 举例:如果是insertAfter则调用了After()方法         
            */
            jQuery( insert[ i ] )[ original ]( elems );
            //把元素添加到ret数组里
            push.apply( ret, elems.get() );
        }
        //构建一个新jQuery对象,以便实现链式
        return this.pushStack( ret );
    };
} );

append()

append():在匹配的元素末尾添加

append: function() {
    return this.domManip( arguments, function( elem ) {
        if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
            var target = manipulationTarget( this, elem );
            target.appendChild( elem );
        }
    });
}

这里用到了一个manipulationTarget函数,用于处理向table元素加入tr但没有tbody的情况,以下是它的源码:

function manipulationTarget( elem, content ) {
    return jQuery.nodeName( elem, "table" ) &&
        //如果加入内容文档碎片,就用它的孩子来判断是不是tr
        jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
        //是tr的话如果存在tbody直接返回,否则创建所属文档的tbody给elem(body)后返回
        elem.getElementsByTagName( "tbody" )[ 0 ] ||
            elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) :
        elem;
}

替换篇

replaceWith

replaceWidth方法对元素进行替换,调用的是原生的replaceChild方法,因为涉及到“删除”这个元素,所以会调用cleanData这个方法:

replaceWith: function() {
    var arg = arguments[0];
    this.domManip(arguments, function(elem) {
        arg = this.parentNode;
        jQuery.cleanData(getAll(this));
        if (arg) {
            arg.replaceChild(elem, this);
        }
    });
    return arg && (arg.length || arg.nodeType) ? this : this.remove();
}   

最后返回的指向也从原来的元素换成了替换后的元素。
然后再来瞅瞅这个cleanData所做的事情(水很深,就先大概了解一下):

cleanData: function( elems ) {
    var data, elem, type,
        special = jQuery.event.special, //自定义事件
        i = 0;

    for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
        if ( acceptData( elem ) ) {
            if ( ( data = elem[ dataPriv.expando ] ) ) {
              //如果data被绑定了事件,就一一移除
                if ( data.events ) {
                    for ( type in data.events ) {
                        if ( special[ type ] ) {
                            //用于移除special中的事件
                            jQuery.event.remove( elem, type );
                        } else {

                            jQuery.removeEvent( elem, type, data.handle );
                        }
                    }
                }
                elem[ dataPriv.expando ] = undefined;
            }
            if ( elem[ dataUser.expando ] ) {
                elem[ dataUser.expando ] = undefined;
            }
        }
    }
}

jQuery.event.special中有load事件,focus事件,blur事件,click事件和beforeunload事件等等…里面做了一些处理去弥补原本的不足。

移除篇

innerText是常用的文本清理方法,火狐不兼容,但提供了类似的textContent方法。
两者的区别

IE中的innerText是需要对innerHTML的值进行:

1.HTML转义
2.HTML解释和CSS样式解释
3.剔除格式信息
4.留下的纯文本

而textContent没有2、3步,在经过了HTML转义之后直接剔除所有html标签后得到的纯文本。

.empty()

为了避免占有本该释放的资源,所以jQuery进行删除前必须要先移除子元素的数据事件处理函数。。

empty: function() {
    var elem,
        i = 0;
    for (;
        (elem = this[i]) != null; i++) {
        if (elem.nodeType === 1) {
            //进行删前准备工作
            jQuery.cleanData(getAll(elem, false));
            //元素内容清空
            elem.textContent = "";
        }
    }
    return this;
}

jQuery.cleanData方法:

通过元素判断上绑定的expando的这个uuid在与之对应的cache中找到数据与事件句柄加以删除。

remove()

.remove() 将元素移出DOM,可以移除自身元素。

/*
* keepData仅供jQuery内部使用
*/
remove: function(selector, keepData ) {
    var elem,
        //如果存在选择器,就先过滤自己,不存在则就是自己
        elems = selector ? jQuery.filter(selector, this) : this,
        i = 0;
    for (;
        (elem = elems[i]) != null; i++) {
        //如果不保留(keepData==false),则先进行事件移除
        if (!keepData && elem.nodeType === 1) {
            jQuery.cleanData(getAll(elem));
        }
        
        //如果元素存在父亲节点
        if (elem.parentNode) {
            //如果保留事件且元素所属文档存在这个元素
            if (keepData && jQuery.contains(elem.ownerDocument, elem)) {
                //在全局脚本中进行记录
                setGlobalEval(getAll(elem, "script"));
            }
            
            //移除元素 elem.parentNode.removeChild(elem);
        }
    }
    return this;
}

因为remove可能是移除自身,所以需要用父亲元素来进行移除。

附加getAll方法:

function getAll( context, tag ) {
   //如果context下存在这个标签元素就获取,如果没有结果就用querySelectorAll去获取,再没结果就是空组
    var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
            context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
            [];
    
    return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
        jQuery.merge( [ context ], ret ) :
        ret;
}

detach()

detach()方法移除被选元素,包括所有文本和子节点。但它
保留jQuery对象中的匹配的元素,因而可以在将来再使用这些匹配的元素,它也
保留所有绑定的事件、附加的数据,这一点与remove() 不同。

所以它和remove方法不一样的就是keepData这个属性,只要传入true就可以了:

detach: function(selector) {
      return this.remove(selector, true);
}

这个方法可以用于删除后又将会被添加进来的元素。

    原文作者:MOCHIKO
    原文地址: https://segmentfault.com/a/1190000014106483
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞