如何理解与简化jQuery的closest函数

87384559 2011-05-18

在实现delegate方法中,有一个很重要的辅助函数叫closest,虽然现在它归类为遍历节点这个模块中。这个函数实现得非常复杂,洋洋洒洒近50行,完全不符合极限编程的规矩。

closest: function( selectors, context ) {     




    var ret = [], i, l, cur = this[0];    




    // Array    




    if ( jQuery.isArray( selectors ) ) {//这分支的过滤逻辑基本与下面的一致   




        var match, selector,   



            matches = {},    


            level = 1;    



        if ( cur && selectors.length ) {    




            for ( i = 0, l = selectors.length; i < l; i++ ) {    



                selector = selectors[i];    



                if ( !matches[ selector ] ) {    



                    matches[ selector ] = POS.test( selector ) ?    



                        jQuery( selector, context || this.context ) :    



                      selector;    


                }    


            }    



            while ( cur && cur.ownerDocument && cur !== context ) {   




                for ( selector in matches ) {    




                    match = matches[ selector ];//这里频繁创建新的jQuery对象与使用is这样复杂的方法,我不觉得其高效到哪里去    




                    if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) {    




                        ret.push({ selector: selector, elem: cur, level: level });    



                    }    


                }    


                cur = cur.parentNode;    


                level++;    


            }    


        }    



        return ret;    



    }   



    // String     




    var pos = POS.test( selectors ) || typeof selectors !== "string" ?    




            jQuery( selectors, context || this.context ) :    



            0;    



    for ( i = 0, l = this.length; i < l; i++ ) {    




        cur = this[i];    




        while ( cur ) {    




            if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {    



                ret.push( cur );    



                break;   




            } else {    



                cur = cur.parentNode;    



                if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {    




                   break;    



                }    


            }    



        }     



    }   


    ret = ret.length > 1 ? jQuery.unique( ret ) : ret;    



    return this.pushStack( ret, "closest", selectors );    



},   

恰逢我也想造个轮子,便去研究它一翻,发现其第一个可以是字符串,元素节点或jQuery对象,还有一个可选参数,上下文。看源码前几句,发现有个分支是判断是否是Array,估计是供内部调用的优化代码,可以无视之。于是其方法简化为:

closest: function( selectors, context ) {    



    var ret = [], i, l, cur = this[0];    




    // 如果字符串包含位置伪类或者是个元素节点,则封装为一个jQuery对象,否则为0(即false的简写,用于快速跳过分支)    




    var pos = POS.test( selectors ) || typeof selectors !== "string" ?    




        jQuery( selectors, context || this.context ) :    



        0;    



    //遍历原jQuery对象的节点    




    for ( i = 0, l = this.length; i < l; i++ ) {    




        cur = this[i];    




        while ( cur ) {    




            //如果是jQuery对象,则判定其是否包含当前节点,否则使用matchesSelector方法判定这个节点是否匹配给定的表达式selectors    




            if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {    




              //是则放入选择器中    



                ret.push( cur );    



                break;   




            } else {    




                //  否则把当前节点变为其父节点    



                cur = cur.parentNode;    



                if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {    




                    break;    



                }    


            }    


        }    


    }    



    //如果大于1,进行唯一化操作    



    ret = ret.length > 1 ? jQuery.unique( ret ) : ret;    



    //将节点集合重新包装成一个新jQuery对象返回    




    return this.pushStack( ret, "closest", selectors );    



},  

由于本人很反感位置伪类,认为其违反选择器的法则之一(由关系选择器隔开的各选择器分组内部,它们的位置是随意的),因此有关位置伪类的逻辑我也去掉了。

closest: function( selectors ) {    



    var ret = [], i, l, cur = this[0];    




    // 如果字符串包含位置伪类或者是个元素节点,则封装为一个jQuery对象,否则为0(即false的简写,用于快速跳过分支)    




    var node =  selectors.nodeType ? selectors :false;    




    var nodes = [].slice.call(this);//将jQuery对象转换为纯数组    




    //遍历原jQuery对象的节点    




    for ( i = 0, l = this.length; i < l; i++ ) {    




        cur = this[i];   




        while ( cur ) {    




            //如果是jQuery对象,则判定其是否包含当前节点,否则使用matchesSelector方法判定这个节点是否匹配给定的表达式selectors    




            if ( obj ? nodes.indexOf(node) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {    




                //indexOf方法在某些浏览器需要自行实现    




                //是则放入选择器中    



                ret.push( cur );    



                break;    




            } else {    




                //  否则把当前节点变为其父节点    



                cur = cur.parentNode;    



                //如果没有父节点(说明其还没有插入DOM树),或不是元素节点,或是文档碎片(说明其刚被移出DOM树)    




                if ( !cur || !cur.ownerDocument || cur.nodeType === 11 ) {    




                    break;    



                }    


            }    


        }    


    }    



    //如果大于1,进行唯一化操作    



    ret = ret.length > 1 ? jQuery.unique( ret ) : ret;    



    //将节点集合重新包装成一个新jQuery对象返回    




    return $(ret);//本人觉得pushStack真是个邪恶的方法,让菜鸟不籽有链下去的欲望,殊不知这是维护的大敌    



},  

注意,jquery1.6中closest方法不再是返回包含一个或零个节点的jQuery对象了,再是对应多个了(因此jQuery官网文档是错误的,没有即时同步这变化.)

<!doctype html>    


 


<html>    


  <head>    


    <title>closest在jquery1.6的改变 by 司徒正美</title>    



    <script src="jquery.js"></script>    



    <script>    



      $(function(){    




        $("p").delegate("strong","click",function(){    




         alert(this.innerHTML)    



        });    



        alert($("strong").closest("p").length)    



      });    


    </script>    


  </head>    


  <body>    


    <p>    


      <strong>使用事件代理1</strong>    


    </p>    


    <p>    


     <strong>使用事件代理2</strong>    


    </p>    


    <p>    


      <strong>使用事件代理3</strong>    


    </p>    


  </body>    


</html>  

下面是我的实现:

相关推荐