您的位置:首页 > Web前端 > JQuery

jQuery源码分析12--回溯处理的设计(设计代码优化)

2015-12-29 16:26 591 查看


回溯处理的设计(设计代码优化)

理解栈存对象的概念,结合游标可以很好的理解

在这一小节我将会带领你们了解jQuery对DOM进行遍历背后的工作机制,这样可以在编写代码时有意识地避免一些不必要的重复操作,从而提升代码的性能。

关于jQuery对象的包装

var $aaron = $("aaron");


通过对sizzle的分析,我们可以得知Query选择器最终都是通过DOM接口实现取值的, 但是通过jQuery处理后返回的不仅仅只有DOM对象,而是一个包装容器,返回jQuery对象:$aaron。

我们来看一下代码:



在jQuery对象中有个prevObject对象,这个是干嘛用的呢?

如果你想知道prevObject是做什么的,咱们首先得先来了解一下jQuery对象栈,jQuery内部维护着一个jQuery对象栈。每个遍历方法都会找到一组新元素(一个jQuery对象),然后jQuery会把这组元素推入到栈中。

而每个jQuery对象都有三个属性:context、selector和prevObject,其中的prevObject属性就指向这个对象栈中的前一个对象,而通过这个属性可以回溯到最初的DOM元素集中。

为了方便理解,我们做几个简单的测试:

下面有一个父元素ul,嵌套了一个li节点:

<ul id="aaron">
    parent
    <li>child</li>
</ul>


我们现给li绑定一个事件,这个很简单,找到ul下面的li,绑定即可:

var aaron = $("#aaron");
    aaron.find('li').click(function(){
        alert(1);     //1
    })


此时我又想给父元素绑定一个事件,我们是不是又要在aaron上绑定一次事件呢?是的,上面代码通过find处理后,此时的上下文是指向每一个li了,所以必须要重新引用aaron元素(li的父元素),然后再绑定click事件:

aaron.click(function(){
      alert(2);     //1
 })


这样会不会很麻烦,所以jQuery引入一个简单的内部寻址的机制,可以回溯到之前的Dom元素集合,通过end()方法可以实现:

aaron.find('li').click(function() {
        alert(1);
}).end().click(function() {
        alert(2);
})


jQuery为我们操作这个内部对象栈提供个非常有用的2个方法

.end()

.addBack()

这里需要指出来可能有些API上是andSelf,因为就Query的api是这样写的,andSelf现在是.addBack()的一个别名。在jQuery1.8和更高版本中应使用.addBack()

源码其实也是这样的
jQuery.fn.andSelf = jQuery.fn.addBack;


调用第一个方法只是简单地弹出一个对象(结果就是回到前一个jQuery对象)。第二个方法更有意思,调用它会在栈中回溯一个位置,然后把两个位置上的元素集组合起来,并把这个新的、组合之后的元素集推入栈的上方。

利用这个DOM元素栈可以减少重复的查询和遍历的操作,而减少重复操作也正是优化jQuery代码性能的关键所在。

实例:

<body>
    
<button id="test1">直接处理</button>
<button id="test2">通过end连贯处理</button> 
    
<ul class="first">
<li class="foo">list item 1</li>
<li>list item 2</li>
<li class="bar">list item 3</li>
</ul>
<ul class="second">
<li class="foo">list item 1</li>
<li>list item 2</li>
<li class="bar">list item 3</li>
</ul>

<script type="text/javascript">
  
$("#test1").click(function(){
   //直接处理
   var foo = $('ul.first').find('.foo');
   foo.css('background-color', 'red')
   foo.find('.bar').css('background-color', 'green'); 
})

$("#test2").click(function(){
  //通过end连贯处理
  $('ul.first').find('.foo').css('background-color', 'red')
  .end().find('.bar').css('background-color', 'green');
})

</script>


附:


end与addBack

大多数jQueryDOM遍历方法来操作jQuery对象实例,并创建一个新的对象,匹配一个不同的DOM元素集合。当发生这种情况时,实际上是新的元素集合被压入到对象内部维护的栈中。每次过滤方法都会被压入栈中。当我们需要返回到前一个状态时,我们可以使用end()进行出栈操作,来返回栈中的前一个状态。

假设页面上有几个列表项:右图所示

end()方法主要用于jQuery的链式属性中。当没有使用链式用法时,我们通常只是调用变量名上的前一个对象,所以我们不需要操作栈。

使用end()时,我们可以一次性调用所有需要的方法:

$('ul.first').find('.foo').css('background-color', 'red').end().find('.bar').css('background-color', 'green');


链式的原理就是要返回当前操作的上下文。

下面的代码是错误的:

$('ul.first').find('.foo').css('background-color', 'red').find('.bar').css('background-color', 'green');


上面的代码因为上下文被切换了,所以执行find(‘bar’)时就出错了。

下面的代码是正确的写法:

$('ul.first').find('.foo').css('background-color', 'red').end().find('.bar').css('background-color', 'green');


首先在链式用法中只在第一个列表中查找样式为 foo 的项目,并将其背景色变成红色。然后end()返回调用find()之前的状态。因此,第二次 find() 将只会查找 <ul class="first"> 中的 '.bar',而不是继续在<li class="foo">中进行查找,结果是将匹配到的元素的背景色变成绿色。上述代码的最终结果是:第一个列表中的第 1 和第 3 个列表项的背景色有颜色,而第二个列表中的任何项目都没有背景色。

总的来说:end方法就是回溯到上一个Dom合集,因此对于链式操作与优化,这个方法还是很有意义的。

源码实现

既然是回溯到上一个DOM合集,那么肯定end方法中返回的就是一个jQuery对象了,所以我们看源码其实就是返回prevObject对象了,如下代码:

end: function() {
     return this.prevObject || this.constructor(null);
 }


prevObject在什么情况下会产生?

在构建jQuery对象的时候,通过pushStack方法构建,如下代码:

jQuery.fn.extend({
    find: function(selector) {

        //...........................省略................................

        //通过sizzle选择器,返回结果集
        jQuery.find(selector, self[i], ret);

        // Needed because $( selector, context ) becomes $( context ).find( selector )
        ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret);
        ret.selector = this.selector ? this.selector + " " + selector : selector;
        return ret;
    }
}




可以看到通过jQuery.find后得到了结果ret这个就是通过纯的DOM节点,那么如果变成一个jQuery对象呢?

接着我们看pushStack对象,作用就通过新的DOM元素去创建一个新的jQuery对象

pushStack: function( elems ) {
    // Build a new jQuery matched element set
    var ret = jQuery.merge( this.constructor(), elems );

    // Add the old object onto the stack (as a reference)
    ret.prevObject = this;
    ret.context = this.context;

    // Return the newly-formed element set
    return ret;
 }


流程解析:

1、首先构建一个新的jQuery对象,因为constructor是指向构造器的,所以这里就等同于调用jQuery()方法了,返回了一个新的jQuery对象;

2、然后用jQuery.merge语句把elems节点合并到新的jQuery对象上;

3、最后给返回的新jQuery对象添加prevObject属性,我们看到prevObject其实还是当前jQuery的一个引用罢了,所以也就是为什么通过prevObject能取到上一个合集的原因了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: