如何优化JQuery each()函数的性能
如果对jQuery这东西只停留在用的层面,而不知其具体实现的话,真的很容易用出问题来。这也是为什么近期我一直不怎么推崇用jQuery,这框架的API设定就有误导人们走上歧途之嫌。
01 |
$.fn.beautifyTable = function (options) { |
02 |
//定义默认配置项,再用options覆盖 |
03 |
return this .each( function () { |
04 |
var table = $( this ), |
05 |
tbody = table.children( 'tbody' ), |
06 |
tr = tbody.children( 'tr' ), |
07 |
th = tbody.children( 'th' ), |
08 |
td = tbody.children( 'td' ); |
09 |
//单独内容的class |
10 |
table.addClass(option.tableClass); |
11 |
th.addClass(options.headerClass); //1 |
12 |
td.addClass(options.cellClass); //2 |
13 |
|
14 |
//奇偶行的class |
15 |
tbody.children( 'tr:even' ).addClass(options.evenRowClass); //3 |
16 |
tbody.children( 'tr:odd' ).addClass(options.oddRowClass); //4 |
17 |
|
18 |
//对齐方式 |
19 |
tr.children( 'th,td' ).css( 'text-align' , options.align); //5 |
20 |
|
21 |
//添加鼠标悬浮 |
22 |
tr.bind( 'mouseover' , addActiveClass); //6 |
23 |
tr.bind( 'mouseout' , removeActiveClass); //7 |
24 |
|
25 |
//点击变色 |
26 |
tr.bind( 'click' , toggleClickClass); //8 |
27 |
}); |
28 |
}; |
总的来说,这段代码不错,思路清晰,逻辑明确,想要做什么也通过注释说得很明白了。但是按作者的说法,当表格中有120行时,IE已经反映脚本运行时间过长了。显然从表现来看,这个函数的效率不高,甚至说极其低下。
于是,开始从代码层面进行分析,这是一个标准的jQuery插件式的函数,有个典型的return this.each(function( ) { 。.. };);形式的代码,如果作者写下这段代码的时候,不是照本宣科不经思考的话,就应该意识到jQuery的一个函数干了什么事。
简单来说,jQuery.fn下的函数,绝大部分是一个each的调用,所谓each,自然是对选择出来的元素进行了遍历,并对某个元素进行了指定的操作。那么看看上面一段代码,进行了多少的遍历,在此就假设只选择了120行,每一行有6列,另加上1行的表头吧:
- 遍历th,添加headerClass,元素数为6。
- 遍历td,添加cellClass,元素数为6*120=720。
- 从所有tr中找出奇数的,需要对所有tr进行一次遍历,元素数为120。
- 遍历奇数的tr,添加evenRowClass,元素数为120/2=60。
- 从所有tr中找出偶数的,需要对所有tr进行一次遍历,元素数为120。
- 遍历偶数的tr,添加oddRowClass,元素数为120/2=60。
- 遍历所有th和td,添加text-align,元素数为120*6+6=726。
- 遍历所有tr,添加mouseover事件,元素数为120。
- 遍历所有tr,添加mouseout事件,元素数为120。
- 遍历所有tr,添加click事件,元素数为120。
为了方便,我们简单地假设,在遍历中访问一个元素耗时为10ms,那么这个函数一共用了多少时间呢?这个函数共遇上了2172个元素,耗时21720ms,即21秒,显然IE确实应该报脚本执行过久了。
知道了效率低下的原因,要从根本上进行解决,自然要想方设法来合并循环,初略一看,按照上边代码中注释里的数字,至少以下几点是可以合并的:
3和4可以合并为一次循环,从120+60+120+60变为120,减少了240。1、2和5可以合并为一次循环,从6+720+726变为726,减少了726。6、7、8可以合并为一次循环,从120+120+120变为120,减少了240。进一步的,3、4和6、7、8一样可以合并为一次循环,继续减少了120。累加一下,我们一共减少了240+726+240+120=1326次元素操作,总计13260ms。在优化之后,我们的函数耗时变为21720-13260=8460ms,即8s。
到这里可能会有一个疑问,从表格的结构上来说,所有的th和td元素肯定都在tr之内,那么为什么不将1、2、5这三步的循环同样放到对tr的循环中,形成一个嵌套的循环,这样不是更加快速吗?
这里之所以没有这么做,主要有2个原因:
其一,无论将1、2、5这三者放在哪里,都不会减少对所有th和td元素的一次访问。
另一方面,$(‘th,td’)这个选择器,在sizzle中会被翻译成2次getElementsByTagName函数的调用,第一次获取所有th,第二次获取所有td,然后进行集合的归并。由于getElementsByTagName是内置函数,在此可以认为该函数是不带循环的,即复杂度为O(1),同样集合的归并使用Array的相关函数,是对内存的操作,复杂度同样为O(1)。
反之,如果在对tr元素的循环中再采用$(‘th,’td)这个选择器,则是在tr元素上调用2次getElementsByTagName,由于无论在哪个元素上调用该函数,函数执行的时间是相同的,因此在循环tr时使用,反而多出了119*2次的函数调用,效率不升反降。
可见,对sizzle选择器的基本知识,也是帮助优化jQuery代码的很重要的一方面。
不要啥都让javascript来做。
根据前面的基本的优化,已经将时间从21秒降到了8秒,但是8秒这个数字显然是无法接受的。
再进一步分析我们的代码,事实上,循环遍历是语言层面上的内容,其速度应该是相当快的。而针对每个元素所做的操作,是jQuery提供的函数,相比遍历来说,才是占去大部分资源的主子。如果说遍历中访问元素用时是10ms的话,不客气地说执行一个addClass至少是100ms级别的消耗。
因此,为了进一步地优化效率,就不得不从减少对元素的操作入手。再仔细地回审代码,发现这个函数有着非常多的对样式的修改,其中至少包括了:
- 给所有th加上class。
- 给所有td加上class。
- 给tr分奇偶行加上class。
- 给所有th和td加上一个text-align样式。
而事实上我们知道,CSS本身就拥有子代选择器,而浏览器原生对CSS的解析,效率远远高于让javascript去给元素一一加上class。
所以,如果对CSS是可控的,那么这个函数就不应该拥有headerClass、cellClass这两个配置项,而是尽可能地在CSS中进行配置:
1 |
.beautiful-table th { /* headerClass的内容 */ } |
2 |
.beautiful-table td { /* cellClass的内容 */ } |
再者,对于tr的奇偶行样式,在部分浏览器下可以使用:nth-child伪类来实现,这方面可以利用特性探测,仅在不支持该伪类的浏览器中使用addClass添加样式。当然如果你仅仅想对IE系列进行优化的话,这一条可以忽略了。
对于:nth-child伪类的探测,可以用以下的思路来进行:创建一个stylesheet,再创建一条规则,如#test span:nth-child(odd) { display: block; }。 创建相应的HTML结构,一个id为test的div,内部放置3个span。
将stylesheet和div一同加入的DOM树中。查看第1和第3个span的运行期display样式,如果是block,则表明支持该伪类。删除创建的stylesheet和div,别忘了缓存探测的结果。最后,对于给所有th和td元素添加text-align样式,也是可以通过css进行优化的。既然不知道添加的是哪个align,那么就多写几个样式:
1 |
/* CSS样式 */ |
2 |
.beautiful-table- center th,.beautiful-table- center td { text-align : center !important ; } |
3 |
.beautiful-table-rightright th,.beautiful-table-rightright td { text-align : rightright !important ; } |
4 |
.beautiful-table- left th,.beautiful-table- left td { text-align : left !important ; } |
5 |
/* javascript */ |
6 |
table.addClass( 'beautiful-table-' + options.align); |
当然,上面所说的优化,是建立在对CSS有控制权的情况下的,如果本身无法接触到CSS样式,比如这是一个通用的插件函数,会被完全无法控制的第三方使用,那么怎么办呢?也不是完全没有办法:
去找页面里的所有CSS规则,比如document.styleSheets。遍历所有规则,把配置项中的headerClass、cellClass等拿出来。提取需要的几个class中的所有样式,再自己组装成新的选择器,如beautiful-table th。使用创建出来的选择器,生成新的stylesheet,加入到DOM树中。那么只给table加上beautiful-table这个class就搞定了。
当然上面的做法其实也蛮消耗时间的,毕竟又要遍历stylesheet,又要创建stylesheet。具体是不是对效率提升有很大的帮助,则依据页面的规模会有不同的效果,是否使用就要看函数设计人员的具体需求了,这里也就是提一种策略。
总的来说,通过尽可能少地执行javascript,将更多的样式化的任务交给CSS,则浏览器的渲染引擎来完成,又可以进一步地优化该函数,假设对addClass、css的调用需要100ms的话,此次优化直接消灭了原有120+726=846次的操作,节约了84600ms的时间(当然有夸张的成分,但是对整个函数的消耗来说,这个确实是很大的一块)。
这篇文章,仅仅是想在jQuery的各个实现的层面上来进行优化,只涉及到了对jQuery整个运行过程的分析、细节介绍和优化方向,并没有提到一些基本之基本的优化方法,比如:先将整个table从DOM树中移除,完成所有的操作之后再放回DOM,减少repaint。将mouseover和mouseout改为mouseenter和mouseleave,减少因为下正确的事件冒泡模型导致的重复的事件函数的执行。对于th、td之类单纯元素的选择,优先考虑使用原生的getElementsByTagName,消灭sizzle分析选择器的时间。
最后,这篇文章只是想说明,对于前端开发人员,虽然浏览器可能是个黑盒,但是很多框架、工具、库都是开放的,在使用之前如果可以进行一定程度的了解,必然有助于个人的技术提升和最终产品的质量优化,“知其然而不知其所以然”是非常忌讳的情况。
如何优化JQuery each()函数的性能的更多相关文章
- KTL 一个支持C++14编辑公式的K线技术工具平台 - 第四版,稳定支持Qt5编程,zqt5语法升级,MA函数提升性能1000%,更多公式算法的内置优化实现。
K,K线,Candle蜡烛图. T,技术分析,工具平台 L,公式Language语言使用c++14,Lite小巧简易. 项目仓库:https://github.com/bbqz007/KTL 国内仓库 ...
- 最有效地优化 Microsoft SQL Server 的性能
为了最有效地优化 Microsoft SQL Server 的性能,您必须明确当情况不断变化时,性能将在哪些方面得到最大程度的改进,并集中分析这些方面.否则,在这些问题上您可能花费大量的时间和精力 ...
- jquery常用函数与方法汇总
1.delay(duration,[queueName]) 设置一个延时来推迟执行队列中之后的项目. jQuery1.4新增.用于将队列中的函数延时执行.他既可以推迟动画队列的执行,也可以用于自定义队 ...
- PHP性能优化学习笔记--语言级性能优化--来自慕课网Pangee http://www.imooc.com/learn/205
使用ab进行压力测试 ab -n行数 -c并发数 url 重点关注下面两点: 1.Request per secend : 每秒可接收的请求数 2.Time per request : 每次请求所耗费 ...
- SQL Server 性能优化之——系统化方法提高性能
SQL Server 性能优化之——系统化方法提高性能 阅读导航 1. 概述 2. 规范逻辑数据库设计 3. 使用高效索引设计 4. 使用高效的查询设计 5. 使用技术分析低性能 6. 总结 1. 概 ...
- oracle 11g亿级复杂SQL优化一例(数量级性能提升)
自从16年之后,因为工作原因,项目中就没有再使用oracle了,最近最近支持一个项目,又要开始负责这块事情了.最近在跑性能测试,配置全部调好之后,不少sql还存在性能低下的问题,主要涉及执行计划的不合 ...
- 优化jQuery选择器
优化jQuery选择器 选择优化比以前更加重要,因为越来越多的浏览器实现了queryselectorall()并承担了将jQuery选择器转移到浏览器的责任.记住这些小技巧可以让你轻松突破学习选择器时 ...
- 图片放大功能插件及jquery.extend函数理解
前端时间,产品提出社区评论中的图片需要有放大功能.感觉可以共用,所以就想整合一个插件,过程中也借鉴了一些例子. 分析下自己的代码思路: var scaleImg = function(opts) { ...
- jquery each函数 break和continue功能
jquery each函数 break和continue功能幸运的是另一个突破,持续一个jQuery循环方式.你可以打破在函数返回一个jQuery参数虚假循环.一个可以继续执行只是在做不指定返回值或返 ...
随机推荐
- linux安装python并安装pip
因为最近要在linux环境下进行python编程,所以就试着去安装了一下,但是网上关于python以及pip的安装说实话有点混乱,所以我今天就把前辈的经验再次总结一下,希望可以给大家提供帮助. pyt ...
- linux实现DNS轮询实现负载平衡
DNS 轮询机制会受到多方面的影响,如:A记录的TTL时间长短的影响:别的 DNS 服务器 Cache 的影响:windows 客户端也有一个DNS Cache.这些都会影响 DNS 轮询的效果.因此 ...
- Java学习笔记十三:Java中的类和对象
Java中的类和对象 一:什么是对象: 总的来说就是"万物皆对象",客观存在的事物皆为对象.是计算机所关注的具体信息. 对象(object)是一件事.一个物体.一个名词,或可以获得 ...
- R语言学习笔记(九):fivenum()与quantile()
fivenum() fivenum(x, na.rm = TRUE) x 为数值型向量,可以包含NA以及Inf,-Inf na.rm = TRUE 默认将NA和NaN去除,但是Inf还保留. five ...
- Coap协议学习笔记-第一篇
1. 物联网应用上一般使用单片机(或者其他SOC),单片机的RAM内存一般只有20KB~~128KB左右,然而一个TCP协议栈可能就20KB,所以只能用UDP,因为UDP相对小很多,然后在UDP上加了 ...
- jmeter结合autoit操作windows程序
需求: 模拟操作下图软件的控件,如拨号和挂机. 1. 下载安装好autoit后,打开finder tool,使用查找工具定位到要模拟操作的控件上,如图: 2.在finder tool中的control ...
- jmeter插件下载
https://jmeter-plugins.org/wiki/Start/ 插件下载好后,将插件lib目录下的jar包放在jmeter安装目录下的lib里,插件ext目录下的jar包放在jmeter ...
- 用travis-ci编译android版nodejs
第一步: fork 第二步: 添加.travis.yml 在repository根目录添加.travis.yml文件,在其中添加以下内容. language: c before_install: - ...
- 修改npm全局安装模式的路径
由于npm全局模块的存放路径及cache的路径默认是放在C盘下,这样肯定会增加C盘的负担,那么如果需要修改其存放路径应该怎么做呢? 第一步:在nodejs安装目录(也可以指定其它目录)下创建”node ...
- jmeter接口测试--获取token
Jmeter进行接口测试-提取token 项目一般都需要进行登陆才能进行后续的操作,登陆有时发送的请求会带有token,因此, 需要使用后置处理器中的正则表达式提取token,然后用BeanShell ...