【翻译】即学即用的Web前端优化技巧 ( Front-end optimizations you can start doing right now )

英文原文 http://odiseo.net/javascript/front-end-optimizations-you-can-do-right-now-on-your-existing-code

thumb

强烈建议各位Web前端开发人员都要“真正”去学习Javascript并且掌握基本的DOM知识。诚然我也不得不赞同,不少基于Javascript特性的小把戏和小技巧根本就不会带来终端用户能明显觉察到的性能提升。话虽如此,我会在这篇文章里分享一些优化技巧,你可以马上在代码里使用这些技巧从而让程序跑的更快。而且从今以后,每当在项目里使用Javascript的时候你都应该想到并实践这些优化。

精明的使用选择器 Use selectors wisely

假设程序里有一个id为#profile-container的div, 你想在其内部选择一个或多个class为myClass的input元素。你可能会很快写出下面这样一个jquery选择器:

$('#profile-container input.myClass')

 

这句可以满足需求,但并不是最好的方法。事实上,$(‘#profile-container’).find(‘input.myClass’)会比上面那句更快。为什么呢?这需要了解下Jquery选择器机制。首先,$(‘#profile-container’)这个选择器可以很快的获得要选取的部分,这样当使用find()的时候,就会限制在一个很有限的查找范围,从而提升了性能。可以参开Jquery源码

Rob Tarr的文章给出的一些测试结果可以印证这点,他还发现如果链式的使用find()逐层查找会更快,比如$(‘.container’).find(‘.main’).find(‘ul.list-1′).find(‘li’)

C9LBtwc.png

seesparkbox.com给出的选择器测试比较的结果

另一种精明的使用选择器提高性能的办法就是明确的的声明需要查找的元素类型。比如,如果我们知道要选择的元素类型的话,$(‘ul.todo’)明显会比$(‘.todo’)快的多。

缓存jQuery选择器的结果 Cache jQuery selector results

这实际上是一个总所周知的技巧,“不要重复”是经典的提升性能的准则和最佳实践。在Greg Franko精彩的幻灯片“jQuery最佳实践”里有一个有趣的例子:

// Set's an element's title attribute using it's current text
 $(".container input#elem").attr("title", $(".container input#elem").text());
// Set's an element's text color to red
 $(".container input#elem").css("color", "red");
// Makes the element fade out
 $(".container input#elem").fadeOut();

问题就在于当我们每次调用$(“.container input#elem”)的时候,jQuery都需要使用一次选择器,也就是要遍历一遍所有的DOM。也是就说“.container input#elem”这个查找过程要重复执行4次!上面的代码应该修改成下面这样

// Stores the live DOM element inside of a variable
 var elem = $("#elem");
// Set's an element's title attribute using it's current text
 elem.attr("title", elem.text());
// Set's an element's text color to red
 elem.css("color", "red");
// Makes the element fade out
 elem.fadeOut();

 

注意这里很“抠门”的使用了选择器,因为我们要查找一个id,所以就没有必要去关心这个id是在某个class或者html元素里了。只需要#elem,这样即简单,也更快。

缓存.length属性 Cache .length property

在Javascript里,每次获取数组的.length属性的时候,都需要进行一次运算。因为,对于下面这样一段代码:

for (var i = 0; i < myArray.length; i++){...}

假设myArray数组大小是10000的话,每次循环计算一次, myArray.length的值就会重复计算10000次。所以你应该按下面这样写:

var arrayLength = myArray.length;
for (var i = 0; i < arrayLength; i++){...}

 

这样,只需要计算一次arrayLength就可以了。

这个真的这么重要么?其实在现代浏览器中,这两种的区别是微乎其微的,因为浏览器引擎已经做了这种优化。那么以后是否就完全不需要考虑这些了么?让我们看一下comp.lang.javascript新闻组的Thomas Lahn的观点(简单但比较哲学,译不出味道来,先放弃):

One should never rely on what one cannot know. You cannot know the runtime environments code once written will be exposed to. There is no good reason to assume the value of the “length” property should be automatically cached for a loop in the first place as it could change by statements in the loop.

One can know instead that avoiding to access a property repeatedly whose value does not change, in favor of a local variable that holds this value, reduces code complexity. That has a chance – and it has been showed – to increase runtime efficiency, IOW to be faster. So it is wise to do that.

最小化DOM操作 Minimize DOM operations

写DOM是很“重”的操作。记住:DOM很慢。如果没意识到这点,你早晚会遇到性能问题。下面是一个让浏览器不堪重负的经典案例:

var toDoList = $("#todoList");
myTasks.forEach(function(task){
  toDoList.append("<li id=" + task.index + ">" + task.name + "</li>");
});

在这里例子里面,我们每次forEach循环都会有读&写DOM的操作。实际上我们可以通过把这些节点存储在一个变量里,等到循环结束以后再一次性的把累积的变量写入DOM,从而规避这种混乱的情况。这样就只需要“附加”操作一次:

var toDoList = $("#todoList");
dynamicItems = "";

myTasks.forEach(function(task){
  dynamicItems += "<li id=" + index + ">" + value + "</li>";
});  
toDoList.append(dynamicItems);

 

避免重复创建对象 Avoid repeated object creation

如果在函数中创建了一个对象的话,那么这个对象在每次函数调用的时候都会被重复创建一次。这可能并不是你的本意,特别是当这个对象是静态的,你本来没准备在任何情况下改变它的时候。让我们看下David Walsh给出的例子

function cleanText(dirty) {
  // Get rid of SCRIPT tags
  clean = dirty.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, "");

  // Do some more cleaning, maybe whitespace, etc.

  return clean;
}

文字记号//实际上是new RegExp的缩写形式,也就是说每次使用类似/ab+c/这样的语句实际上就同时创建了一个正则表达式对象

//both expressions are equivalent
var re = /ab+c/;
var re = new RegExp("ab+c");

在本例中,我们其实并不需要每次都创建一个正则表达式对象。可以像这面这样通过在函数外创建对象,然后在函数中调用(through a closure scope–不太会翻译这句

var scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
function cleanText(dirty) {
  // Get rid of SCRIPT tags
  clean = dirty.replace(scriptRegex, "");

  // Do some more cleaning, maybe whitespace, etc.

  return clean;
}

 

委托事件监听 Delegate event listeners

给单独的元素使用事件监听会占用很多内存。如果给动态创建的很多新元素一一绑定事件监听的话代价会非常高。

document.querySelector('#todoList li').addEventListener("click", function() {
    alert("Clicked on a task"); 
});

如果上面代码中的#todoList里有10000个li元素的话,也就是说会有10000个事件监听器。采用事件托管的方法可以就可以不用给每个单独的元素使用监听器,而是在这些元素的父节点上使用一个监听器就可以了。上面的例子可以修改成:

document.querySelector('#todoList').addEventListener('click', function(e) { 
    if (e.target &amp;&amp; e.target.tagName == 'LI') { 
        alert("Clicked on a task"); 
    } 
});

用jQuery的话更简单了

$("#todoList").on("click", 'li', function() {
    alert("Clicked on a task"); 
});

 

只需要使用选择器的话,别用jQuery了 Stop using jQuery when you only need selectors

总所周知, jQuery是遍历DEOM的最佳工具。但是如果你的项目只需要使用jQuery进行这一个工作的,就需要慎重权衡下是否值得因为这个需求而加载一个外部 库文件。因为使用document.querySelectorAll也可以和使用jQuery一样实现这些基本的选择功能。是的,你没看错,完全可以使 用浏览器内置功能实现像document.querySelectorAll(‘.content ul li’)这样的日常选择器操作。你说这样写看起来又长又丑?让我们看一下Burke Holland在他的文章5 Things You Should Stop Doing With jQuery里的方法:

<div class="container">
  <ul>
    <li id="pink">Pink</li>
    <li id="salmon">Salmon</li>
    <li id="blue">Blue</li>
    <li id="green">Green</li>
    <li id="red">Red</li>
  </ul>  
</div>

<script>
  // create a global '$' variable
  window.$ = function(selector) {
    return document.querySelector(selector);
  };

  (function() {
    // select item1 by id and change it's background color to salmon
    var item = $("#salmon").style.backgroundColor="salmon";
    console.log(item);
  }());  
</script>

 

这样写就可以继续使用你所钟爱的$(‘mySelector’)结构了。

那么querySelectorAll和jQuery的选择器相比究竟有多快呢?这要取决于不同的浏览器,但至少快5倍!尽管JQuery在document.querySelectorAll方法存在的情况下其实也是会自动调用它来实现选择器,但是我们是不是还要权衡下这多加载的90KB(jquery.mini.js文件)呢?

还想进一步学习么?我推荐进行在Javascript中更高效的进行分析和管理数据结构(profiling and managing data structures efficiently)这方面的学习。我以后可能会围绕这块写一些东西,也可以参考下这篇文章

本文地址: http://awebird.com/blog/art/55

Leave a Reply