建站学 - 轻松建站从此开始!

建站学-个人建站指南,网页制作,网站设计,网站制作教程

分享一次不是很成功的jQuery优化

时间:2011-04-08 09:48来源: 作者: 点击:
我经常抱怨jQuery的DOM操作性能并不优秀,并且经常尝试用一些方法去进行优化,但是越是优化,越是沮丧地发现jQuery其实已经做得很好,从使用者的角度能够进行的优化实在有限(这并不意味着jQuery的性能是优秀的, 反之只能说它是一个相对封闭的库,无法从外部介入进行优

本文记录一位朋友优化jQuery的过程,是一次不是很成功的jQuery优化。我经常抱怨jQuery的DOM操作性能并不优秀,并且经常尝试用一些方法去进行优化,但是越是优化,越是沮丧地发现jQuery其实已经做得很好,从使用者的角度能够进行的优化实在有限(这并不意味着jQuery的性能是优秀的, 反之只能说它是一个相对封闭的库,无法从外部介入进行优化)。这篇文章就记录一次失败的优化经历。

优化思想

这一次优化的思想来自于数据库。在数据库优化的时候,我们常会说“将大量的操作放在一个事务中一起提交,能有效提高效率”。虽然对数据库不了解的我并不知道其原因,但是“事务”的思想却为我指明了方向(虽然是错的……)。

因此我尝试将“事务”这一概念引入到jQuery中,通过“打开”和“提交”事务,从外部对jQuery进行一些优化,其最重要的在于减少each函数的循环次数。

众所周知,jQuery的DOM操作,以get all, set first为标准,其中用于设置DOM属性/样式的操作,几乎都是对选择出来的元素的一次遍历,jQuery.access函数就是其中最核心的部分,其中用于循环的代码如下:

// Setting one attribute
if ( value !== undefined ) {
    // Optionally, function values get executed if exec is true
    exec = !pass && exec && jQuery.isFunction(value);

    for ( var i = 0; i < length; i++ ) {
        fn(
            elems[i], 
            key, 
            exec ? value.call(elems[i], i, fn(elems[i], key)) : value, 
            pass
        );
    }

    return elems;
}

比如jQuery.fn.css函数就是这样的:

jQuery.fn.css = function( name, value ) {
    // Setting 'undefined' is a no-op
    if ( arguments.length === 2 && value === undefined ) {
        return this;
    }

    return jQuery.access( this, name, value, true, function( elem, name, value ) {
        return value !== undefined ?
            jQuery.style( elem, name, value ) :
            jQuery.css( elem, name );
	});
};

因此,下面这样的代码,假设被选择的div元素有5000个,则要循环访问10000个节点:

jQuery('div').css('height', 300).css('width', 200);

而在我的想法中,在一个“事务”中,可以如数据库的操作一般,通过保存所有的操作,在“提交事务”的时候统一进行,将10000次节点访问,减少至5000次,相当于提升了“1倍”的性能。

简单实现

“事务”式的jQuery操作中,提供2个函数:

  • begin:打开一个“事务”,返回一个事务的对象。该对象拥有jQuery的所有函数,但是调用函数并不会立刻生效,只有在“提交事务”后才会生效。
  • commit:提交一个“事务”,保证所有事先调用过的函数都生效,交返回原本的jQuery对象。

实现起来也很方便:

  1. 创建一个“事务对象”,复制jQuery.fn上所有函数到该对象中。
  2. 当调用某个函数时,在预先准备好的“队列”中添加调用的函数名和相关参数。
  3. 当提交事务时,对被选择的元素进行一次遍历,对遍历中的每个节点应用“队列”中的所有函数。

简单地代码如下:

var slice = Array.prototype.slice;
jQuery.fn.begin = function() {
    var proxy = {
            _core: c,
            _queue: []
        },
        key,
        func;
    //复制jQuery.fn上的函数
    for (key in jQuery.fn) {
        func = jQuery.fn[key];
        if (typeof func == 'function') {
            //这里会因为for循环产生key始终是最后一个循环值的问题
            //因此必须使用一个闭包保证key的有效性(LIFT效应)
            (function(key) {
                proxy[key] = function() {
                    //将函数调用放到队列中
                    this._queue.push([key, slice.call(arguments, 0)]);
                    return this;
                };
            })(key);
        }
    }
    //避免commit函数也被拦截
    proxy.commit = jQuery.fn.commit;
    return proxy;
};

jQuery.fn.commit = function() {
    var core = this._core,
        queue = this._queue;
    //仅一个each循环
    core.each(function() {
        var i = 0,
            item,
            jq = jQuery(this);
        //调用所有函数
        for (; item = queue[i]; i++) {
            jq[item[0]].apply(jq, item[1]);
        }
    });
    return this.c;
};

测试环境

测试使用以下条件:

  • 5000个div放在一个容器(<div id="container"></div>)中。
  • 使用$('#container>div')选择这5000个div。
  • 每个div要求设置一个随机背景色(randomColor函数),和800px以下的随机宽度(randomWidth函数)。

参加测试的调用方法有3个:

  • 正常使用法:

    $('#container>div')
        .css('background-color', randomColor)
        .css('width', randomWidth);
  • 单次循环法:

    $('#container>div').each(function() {
        $(this).css('background-color', randomColor).css('width', randomWidth);
    });
  • 事务法:

    $('#container>div')
        .begin()
            .css('background-color', randomColor)
            .css('width', randomWidth)
        .commit();
  • 对象赋值法:

    $('#container>div').css({
        'background-color': randomColor,
        'width': randomWidth
    });

测试浏览器选择Chrome 8系列(用IE测就直接挂了)。

悲伤的结果

原本的预测结果是,单次循环法的效率远高于正常使用法,同时事务法虽然比单次循环法慢一些,但应该比正常使用法更快,而对象赋值法其实是jQuery内部支持的单次循环法,效率应该是最高的。

然而遗憾的是,结果如下:

正常使用法 单次循环法 事务法 对象赋值法
18435ms 18233ms 18918ms 17748ms

从结果上看,事务法成了最慢的一种方法。同时单次循环与正常使用并没有明显的优势,甚至依赖jQuery内部实现的对象赋值法也没有拉开很大的差距。

由于5000个元素的操作已经是非常庞大的循环,如此庞大的循环也没能拉开性能的差距,平时最常用的10个左右的元素操作更不可能有明显的优势,甚至还可能将劣势扩大化。

究其原因,由于本身单次循环法就没有明显的性能提升,因此依赖于单次循环,并是在单次循环之上进行外部构建的事务法,自然是在单次循环的基础上还需要额外增加创建事务对象、保存函数队列、遍历函数队列等开销,结果败给正常使用法也在情理之中。

至此,也算是可以宣布模仿“事务”的优化之道的失败。但是对这个结果却还能进一步地分析。

(责任编辑:admin)

织梦二维码生成器
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片