记一次失败的jQuery优化尝试

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

优化思想

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

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

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


  1. // Setting one attribute  
  2. if ( value !== undefined ) {  
  3.     // Optionally, function values get executed if exec is true  
  4.     exec = !pass  exec  jQuery.isFunction(value);  
  5.  
  6. for ( var i = 0; i  length; i++ ) {  
  7. fn(  
  8. elems[i],  
  9. key,  
  10. exec ? value.call(elems[i], i, fn(elems[i], key)) : value,  
  11. pass  
  12. );  
  13. }  
  14. return elems; 

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


  1. jQuery.fn.css = function( name, value ) {  
  2.     // Setting 'undefined' is a no-op  
  3.     if ( arguments.length === 2  value === undefined ) {  
  4.         return this;  
  5.     }  
  6.  
  7. return jQuery.access( this, name, value, true, function( elem, name, value ) {  
  8. return value !== undefined ?  
  9. jQuery.style( elem, name, value ) :  
  10. jQuery.css( elem, name );  
  11. });  
  12. }; 

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


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

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

简单实现

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

 ◆  begin:打开一个事务,返回一个事务的对象。该对象拥有jQuery的所有函数,但是调用函数并不会立刻生效,只有在提交事务后才会生效。

 ◆ commit:提交一个事务,保证所有事先调用过的函数都生效,交返回原本的jQuery对象。

实现起来也很方便:

 ◆ 创建一个事务对象,复制jQuery.fn上所有函数到该对象中。

 ◆ 当调用某个函数时,在预先准备好的队列中添加调用的函数名和相关参数。

 ◆ 当提交事务时,对被选择的元素进行一次遍历,对遍历中的每个节点应用队列中的所有函数。

简单地代码如下:


  1. var slice = Array.prototype.slice;  
  2. jQuery.fn.begin = function() {  
  3.     var proxy = {  
  4.             _core: c,  
  5.             _queue: []  
  6.         },  
  7.         key,  
  8.         func;  
  9.     //复制jQuery.fn上的函数  
  10.     for (key in jQuery.fn) {  
  11.         func = jQuery.fn[key];  
  12.         if (typeof func == 'function') {  
  13.             //这里会因为for循环产生key始终是最后一个循环值的问题  
  14.             //因此必须使用一个闭包保证key的有效性(LIFT效应)  
  15.             (function(key) {  
  16.                 proxy[key] = function() {  
  17.                     //将函数调用放到队列中  
  18.                     this._queue.push([key, slice.call(arguments, 0)]);  
  19.                     return this;  
  20.                 };  
  21.             })(key);  
  22.         }  
  23.     }  
  24.     //避免commit函数也被拦截  
  25.     proxy.commit = jQuery.fn.commit;  
  26.     return proxy;  
  27. };  
  28.  
  29. jQuery.fn.commit = function() {  
  30. var core = this._core,  
  31. queue = this._queue;  
  32. //仅一个each循环  
  33. core.each(function() {  
  34. var i = 0,  
  35. item,  
  36. jq = jQuery(this);  
  37. //调用所有函数  
  38. for (; item = queue[i]; i++) {  
  39. jq[item[0]].apply(jq, item[1]);  
  40. }  
  41. });  
  42. return this.c;  
  43. }; 

测试环境

测试使用以下条件:

 ◆ 5000个div放在一个容器(div id="container"/div)中。

 ◆使用$(#containerdiv)选择这5000个div。

 ◆每个div要求设置一个随机背景色(randomColor函数),和800px以下的随机宽度(randomWidth函数)。

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

 ◆正常使用法:


  1. $('#containerdiv')  
  2.     .css('background-color', randomColor)  
  3.     .css('width', randomWidth); 

 ◆单次循环法:


  1. $('#containerdiv').each(function() {  
  2.     $(this).css('background-color', randomColor).css('width', randomWidth);  
  3. }); 

 ◆事务法:


  1. $('#containerdiv')  
  2.     .begin()  
  3.         .css('background-color', randomColor)  
  4.         .css('width', randomWidth)  
  5.     .commit(); 

 ◆对象赋值法:


  1. $('#containerdiv').css({  
  2.     'background-color': randomColor,  
  3.     'width': randomWidth  
  4. }); 

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

悲伤的结果

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

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

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

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

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

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

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

性能在哪里

首先,从代码的使用上来分析,将正常使用法和测试中最快的对象赋值法进行比较,可以说两者的差距仅在于循环的元素个数的不同(这里抛开了jQuery的内部问题,事实上jQuery.access的糟糕实现也确实有拖对象赋值法后腿之嫌,好在并不严重),正常使用法是10000个元素,对象赋值法是5000个元素。因此可以简单地认为,18435 17748 = 687ms是循环5000个元素的耗时,这占到整个执行过程的3.5%左右,并不是整个执行过程的主干,其实真的没有优化的必要。

那么另外96.5%的开销去了哪里呢?谨记Doglas的一句话,事实上Javascript并不慢,慢的是DOM的操作。其实剩下96.5%的开销中,除去函数调用等基本的消耗,至少有95%的时间是用在了DOM元素的样式被改变后的重新渲染之上。

发现了这个事实之后,其实也就有了更正确的优化方向,也是前端性能中的基本原则之一:在修改大量子元素时,先将根父DOM节点移出DOM树。因此如果使用以下的代码再进行测试:


  1. //没有重用$('#container')已经很糟糕了  
  2. $('#container').detach().find('div')  
  3.     .css('background-color', randomColor)  
  4.     .css('width', randomWidth);  
  5. $('#container').appendTo(document.body); 

测试结果始终停留在900ms左右,与前面的数据完全不在一个数量级之上,真正的优化成功。

教训和总结

 ◆千万要找到正确的性能瓶颈再进行优化,盲目的猜测只会导致走上错误而偏激的道路。

 ◆ 数据说话,数据面前谁也别说话!

 ◆不认为事务这个方向是错误的,如果jQuery原生就能支持事务这样的概念,会不会有其他的点可以优化?比如一个事务自动会将父元素脱离出DOM树之类的

原文链接:http://www.otakustay.com/a-failure-in-jquery-optimization/

【编辑推荐】

  1. jQuery 1.5正式版发布 五大变化引人注目
  2. 18个最新最给力的jQuery教程分享
  3. 使用 jQuery 简化 Ajax 开发
  4. jQuery实现表格数据的动态添加与统计
  5. jQuery1.5新特征之插件机制的救赎

【责任编辑:陈贻新 TEL:(010)68476606】

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索jquery
, 循环队列
, 对象
, 函数
, 优化
, 循环
, 事务
, jquery' is undefined
, javascriptfor循环jquery
, #循环赋值
, 左右循环
, jq遍历select
, jq创建节点
jq如何创建节点
ps请尝试不优化存储、jquery性能优化、针对jquery的优化方法、jquery 优化、jquery优化方法,以便于您获取更多的相关知识。

时间: 2024-11-02 16:29:53

记一次失败的jQuery优化尝试的相关文章

一次失败的jQuery优化尝试小结_jquery

(这并不意味着jQuery的性能是优秀的, 反之只能说它是一个相对封闭的库,无法从外部介入进行优化).这篇文章就记录一次失败的优化经历. 优化思想 这一次优化的思想来自于数据库.在数据库优化的时候,我们常会说"将大量的操作放在一个事务中一起提交,能有效提高效率".虽然对数据库不了解的我并不知道其原因,但是"事务"的思想却为我指明了方向(虽然是错的--). 因此我尝试将"事务"这一概念引入到jQuery中,通过"打开"和&quo

记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题

原文:记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题 最近有项目反应,在服务器CPU使用较高的时候,我们的事件查询页面非常的慢,查询几条记录竟然要4分钟甚至更长,而且在翻第二页的时候也是要这么多的时间,这肯定是不能接受的,也是让现场用SQLServerProfiler把语句抓取了上来. 用ROW_NUMBER()进行分页 我们看看现场抓上来的分页语句: select top 20 a.*,ag.Name as AgentServerName,,d.Name a

ubuntu 安装软包失败-ubuntu 安装中下载失败取消后每次安装其他程序时下载失败的程序总是尝试下载安装

问题描述 ubuntu 安装中下载失败取消后每次安装其他程序时下载失败的程序总是尝试下载安装 怎样取消该安装程序,后每次安装其他程序时下载失败的程序总是尝试下载安装. 解决方案 卸载干净这个没装好的程序,从包管理器中删除

Jquery优化效率 提升性能解决方案_jquery

例如有一段HTML代码: 1.总是从ID选择器开始继承 以下是引用片段:<div id="content">  <form method="post" action="#">  <h2>交通信号灯</h2>  <ul id="traffic_light">  <li><input type="radio" class="

Jquery优化技巧

 1.优化使用id与标记选择器: 访问dom元素最快速度是:id,其次是元素的标记(tag),依次是类别(class) 2.使用jquery对象缓存: 所谓对象缓存就是,在使用jquery对象时,先尽量使用变量保存对象名,然后通过变量的方法相应操作 例如: $("#main").css("height","200px"); $("#main").click(function(){}); $("#main")

冻结时间倒数前一小时,记一次步步惊心的SQL优化

作者介绍 黄浩:从业十年,始终专注于SQL.十年一剑,十年磨砺.3年通信行业,写就近3万条SQL:5年制造行业,遨游在ETL的浪潮:2年性能优化,厚积薄发自成一家.   9月版本是一个大版本,上上下下都在紧锣密鼓地张罗着.   9月10日版本上线,8日开始,能明显的感觉到大战前战鼓擂动人喊马嘶的紧张氛围.项目组人头簇动,奔走如织:邮箱内,关于BUG单通报及处理意见的邮件,在这个骄阳似火的南方,犹如冷冽寒冬时北方的雪花般漫天纷飞.     14:40  主动出击   快下午三点钟的时候,一片雪花悄

SSD 下的 MySQL IO 优化尝试

1.背景 在阅读这篇文章之前,读者需要注意的是,为了维护隐私,用 MySQL 服务器的 D 段代替完整 IP,并且略去一些私密信息. A 项目,因 I/O 出现规律性地剧烈波动.每 15 分钟落地一次,innodbBuffPoolPagesFlushed 参数监控波峰和波谷交替出现,磁盘 I/O 同样如此,并且 until 达到 100%.经过排查,排除了触发器.事件.存储过程.前端程序定时器.系统 crontab 的可能性.最终定位为 InnoDB 日志切换,但是否完全是日志造成的影响,还有待

jQuery异步调用页面后台实例分析

jQuery调用页面后台方法‏如下: 给出了两个简单的例子,无参数的和有参数的,返回的都是json数据. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="JqueryCSMethodForm.aspx.cs" Inherits="JQuerWeb.JqueryCSMethodForm" %>   <!DOCTYPE html PUBLIC &

jquery性能优化高级技巧_jquery

有时候我们在书写jquery的时候,为了书写代码方便,往往忽略了程序执行过程中,给客户端带来的压力.随之而来的就是在某些低端浏览器或者低端电脑上运行速度缓慢,甚至无法运行等问题. 因此我们有必要对我们自己书写的jquery代码进行优化,以达到更快捷.更流畅的运行效果. jquery性能优化高级技巧,下面主要从七个方面对jquery性能优化做介绍: 1.通过CDN(Content Delivery Network)引入jQuery库 2.减少DOM操作 3.适当使用原生JS 4.选择器优化 5.缓