A-A+

jquery代码优化详解

2016年01月07日 web前端设计 暂无评论 阅读 5 views 次

仅仅是想在jquery的各个实现的层面上来进行优化,只涉及到了对jquery整个运行过程的分析、细节介绍和优化方向,并没有提到一些基本之基本的优化方法,比如:

先将整个table从dom树中移除,完成所有的操作之后再放回dom,减少repaint。

将mouseo教程ver和mouseout改为mouseenter和mouseleave,减少因为下正确的事件冒泡模型导致的重复的事件函数的执行。

对于th、td之类单纯元素的选择,优先考虑使用原生的getelementsbytagname,消灭sizzle分析选择器的时间。

  1. $.fn.beautifytable = function(options) {  
  2.     //定义默认配置项,再用options覆盖  
  3.     return this.each(function() {  
  4.         var table = $(this),  
  5.             tbody = table.children('tbody'),  
  6.             tr = tbody.children('tr'),  
  7.             th = tbody.children('th'),  
  8.             td = tbody.children('td');  
  9.               
  10.         //单独内容的class  
  11.         table.addclass(option.tableclass);  
  12.         th.addclass(options.headerclass); //1  
  13.         td.addclass(options.cellclass); //2  
  14.         //奇偶行的class  
  15.         tbody.children('tr:even').addclass(options.evenrowclass); //3  
  16.         tbody.children('tr:odd').addclass(options.oddrowclass); //4  
  17.         //对齐方式  
  18.         tr.children('th,td').css教程('text-align', options.align); //5  
  19.         //添加鼠标悬浮  
  20.         tr.bind('mouseover', addactiveclass); //6  
  21.         tr.bind('mouseout', removeactiveclass); //7  
  22.         //点击变色  
  23.         tr.bind('click', toggleclickclass); //8  

开始从代码层面进行分析,这是一个标准的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代码的很重要的一方面。

不要啥都让网页特效来做

根据前面的基本的优化,已经将时间从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中进行配置:

.beautiful-table th { /* headerclass的内容 */ }

.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-aligncenter !important; }  
  3. .beautiful-table-rightright th,.beautiful-table-rightright td { text-alignrightright !important; }  
  4. .beautiful-table-left th,.beautiful-table-left td { text-alignleft !important; }  
  5. /* javascript */  

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的时间(当然有夸张的成分,但是对整个函数的消耗来说,这个确实是很大的一块)。

标签:

给我留言