提高QWrap性能的决窍

近日,阮一峰同学参考jquery团队的Addy Osmani的一篇《提高jQuery性能的诀窍》整理了一份《JQuery的最佳实现》,文中提到很多jquery使用者可能会用到的搞高性能的技巧。其实在QWrap的使用中,也会有很多类似的问题,在这里就生吞活剥的来一篇《提高QWrap性能的决窍》。

百度百科:生吞活剥 喻生硬地抄袭或机械地搬用经验、方法、理论等。也指生拉硬扯。

总之,本文结构与大部分内容系抄袭阮同学的那篇文章。

《提高QWrap性能的决窍》

1. 使用最新版本的QWrap
QWrap的最新稳定版本是1.0,可以关注github.com/wedteam/qwrap上的更新,


2. 用对选择器

与Jquery一样,在QWrap中,你可以用多种选择器,选择同一个网页元素。每种选择器的性能是不一样的,你应该了解它们的性能差异。

(1)最快的选择器:id选择器和元素标签选择器

举例来说,下面的语句性能最佳:

  W('#id')
  W(
'form')
  W(
'input')

遇到这些选择器的时候,QWrap内部会自动调用浏览器的原生方法(比如getElementById()),所以它们的执行速度快。

(2)较慢的选择器:class选择器

W('.className')的性能,取决于不同的浏览器。

Firefox、Safari、Chrome、Opera浏览器,都有原生方法querySelectorAll(),所以速度并不慢。但是,IE5-IE7都没有部署这个方法,所以这个选择器在IE中会相当慢。

(3)最慢的选择器:伪类选择器和属性选择器

先来看例子。找出网页中所有的隐藏元素,就要用到伪类选择器:

  W(':nth-child(2n)')

属性选择器的例子则是:

  W('[attribute=value]')

这两种语句是最慢的,因为浏览器没有针对它们的原生方法。一些浏览器的新版本,增加了querySelector()和querySelectorAll()方法,但是QWrap.Selector也采用了保守策略,在出现伪类选择器与属性选择器时,也并没有完全使用querySelectorAll()。
使用querySelector的具体策略是:只有关系符(“,”“ ”“>”“+”“~”)与快捷选择器(div#id.className)。

3. 理解子元素和父元素的关系

下面六个选择器,都是从父元素中选择子元素。你知道哪个速度最快,哪个速度最慢吗?

  W(g('parent')).query('>.child')
  W(
'#parent > .child')
  W(
'.child', g('parent'))
  W(g(
'parent')).query('.child')
  W(
'#parent .child')
  W(
'.child', W('#parent'))

我们一句句来看。

W(g('parent')).query('>.child') 与 W('#parent > .child')的意图是,在#parent的儿子里找.child,不包括孙子曾孙玄孙等。
 网上传说很多Sizzle的顺序是从右往左的,其实不尽然,如果是从右往左,“W('#parent > .child')”会明显慢于“W(g('parent')).query('>.child')”,事实上,选择器的实现者都会在某些情况下调整或打乱查找方向,例如出现#id时,会从#id所在处打断成两个selector。当然QW.Selector也会这么做,这样做的结果就是,上面两者的效率相差不大。
 W(g('parent')).query('>.child') getElementById后进行W装箱,装箱后querySelectorAll('>child'),后来将结果再进行W装箱。
 W('#parent > .child') 一次查询,将结果进行W装箱。在不支持querySelectorAll的浏览器里,查询中也会用到getElementById。分析selector字符串的时间与装箱时间没多大区别。
 这里提到了W装箱,其实就是把一个元素或是一个元素数组,变成一个NodeW对象。相关介绍,参见QWrap系列介绍中的Wrap部分。
以下几个是在子孙中查找.child元素。
(1) W('.child', g('parent'))---- 一次getElementById,一次query,一次W装箱,效率最快
(2) W('#parent .child')----一次query,但是query里需要解析出getElementById,然后再查询,最后将结果装箱,略慢于上一种,不过,这种写法是最简写法哦。
(3) W(g('parent')).query('.child') 一次getElementById,随后装箱,再query一次,再对结果装箱,与2的效率相差不大
(4) W('.child', W('#parent')) 两次query,两次装箱,会比2、3略慢
事实上,在QWrap的Selector的优化三条里有说明:最左边的自选器要带tagName,所以,如果确定要找的是div的话,最好是能这么写:W('#parent div.child')

4. 不要过度使用W

W速度再快,也无法与原生的Dom操作方法相比。所以有原生方法可以使用的场合,尽量避免使用W。

请看下面的例子,为a元素绑定一个处理点击事件的函数:

  W('a').click(function(){
    alert(W(
this).attr('id'));
  });

这段代码的意思是,点击a元素后,弹出该元素的id属性。为了获取这个属性,必须进行W装箱,而后attr('id')。

事实上,这种处理完全不必要。更正确的写法是,直接采用原生写法,调用this.id:

  W('a').click(function(){
    alert(
this.id);
  });

this.id的速度会比W(this).attr('id')快了N多倍。

5. 做好缓存

选中某一个网页元素,是开销很大的步骤。所以,使用选择器的次数应该越少越好,并且尽可能缓存选中的结果,便于以后反复使用。

比如,下面这样的写法就是糟糕的写法:

  W('#top').query('p.classA');
  W(
'#top').query('p.classB');

更好的写法是:

  var cached = W('#top');
  cached.query(
'p.classA');
  cached.query(
'p.classB');


6. 使用链式写法

像jQuery一样,QWrap里支持链式写法。

  W('div').css('color','red').query('h3').item(2).html('Hello');

采用链式写法时,可以省去许多变量定义。

7. 事件的委托处理(Event Delegation)

javascript的事件模型,采用"冒泡"模式,也就是说,子元素的事件会逐级向上"冒泡",成为父元素的事件。

利用这一点,可以大大简化事件的绑定。比如,有一个表格(table元素),里面有100个格子(td元素),现在要求在每个格子上面绑定一个点击事件(click),请问是否需要将下面的命令执行100次?

  W("td").on("click", function(){
    $(
this).toggleClass("click");
  });

回答是不需要,我们只要把这个事件绑定在table元素上面就可以了,因为td元素发生点击事件之后,这个事件会"冒泡"到父元素table上面,从而被监听到。

因此,这个事件只需要在父元素绑定1次即可,而不需要在子元素上绑定100次,从而大大提高性能。这就叫事件的"委托处理",也就是子元素"委托"父元素处理这个事件。

具体的写法是:

  $("#table").delegate("td", "click", function(){
    $(
this).toggleClass("click");
  });


委托处理会提高事件绑定效率,但是会降低事件发生后的运行效率,因为需要递归向上寻亲并matchesSelector。

 

8. 少改动DOM结构

改动DOM结构开销很大,因此不要频繁使用.appendChild()、.insertBefore()和.insetAfter()这样的方法。


9. 正确处理循环

循环总是一种比较耗时的操作,原生的for循环会比forEach/map少一层函数调用,所以效率会好一些。
在使用forEach/map时,也需要注意回调函数的效率。
例如:

var values = W('input[name=aaa][checked]').map(function(el){
return W(el).val();
});

更快一点的写法是:

var values = W('input[name=aaa][checked]').map(function(el){
return el.value;
});

QWrap还有意外的惊喜哦,这里有一种更快更省的写法:

var values = W('input[name=aaa][checked]').getValueAll();


10. 尽量少生成W对象

W对象是对一个或一组节点进行装箱操作后产生的NodeW对象,是一个对象实例,会占用不少资源。所以,尽量少生成W对象。

举例来说,许多节点方法都有三个版本:NodeW原型方法、NodeW静态方法(可以是数组、节点或节点id)、Dom静态方法(是只针对节点或节点id)。
例如,以下代码可以获取value:

W('#myId').val(); //会产生NodeW实例
W('#myId').getValue(); //会产生NodeW实例
W.getValue(g('myId'));
W.getValue(
'myId');
Dom.getValue(
'myId');

事实上,后面的三种方法都可以得到不会产生W实例,效率都比前两种方法快。但是,我们并不推荐使用他们。
因为,很多情况下,效率问题经常是伪命题。
也就是说:在发现有效率问题之前,我们完全可以大胆的用最简省的写法。因为这样我们可以少敲几个字。------我们都是爱偷懒的程序员。

原文地址:https://www.cnblogs.com/jkisjk/p/qwrap_nodew_best_practices.html