[翻译]High Performance JavaScript(025)

第八章  Programming Practices  编程实践

 

    Every programming language has pain points and inefficient patterns that develop over time. The appearance of these traits occurs as people migrate to the language and start pushing its boundaries. Since 2005, when the term "Ajax" emerged, web developers have pushed JavaScript and the browser further than it was ever pushed before. As a result, some very specific patterns emerged, both as best practices and as suboptimal ones. These patterns arise because of the very nature of JavaScript on the Web.

    每种编程语言都有痛点,而且低效模式随着时间的推移不断发展。其原因在于,越来越多的人们开始使用这种语言,不断扩种它的边界。自2005年以来,当术语“Ajax”出现时,网页开发者对JavaScript和浏览器的推动作用远超过以往。其结果是出现了一些非常具体的模式,即有优秀的做法也有糟糕的做法。这些模式的出现,是因为网络上JavaScript的性质决定的。

 

Avoid Double Evaluation  避免二次评估

 

    JavaScript, like many scripting languages, allows you to take a string containing code and execute it from within running code. There are four standard ways to accomplish this: eval(), the Function() constructor, setTimeout(), and setInterval(). Each of these functions allows you to pass in a string of JavaScript code and have it executed. Some examples:

    JavaScript与许多脚本语言一样,允许你在程序中获取一个包含代码的字符串然后运行它。有四种标准方法可以实现:eval(),Function()构造器,setTimeout()和setInterval()。每个函数允许你传入一串JavaScript代码,然后运行它。例如:

 

var num1 = 5,
num2 = 6,
//eval() evaluating a string of code
result = eval("num1 + num2"),
//Function() evaluating strings of code
sum = new Function("arg1", "arg2", "return arg1 + arg2");
//setTimeout() evaluating a string of code
setTimeout("sum = num1 + num2", 100);
//setInterval() evaluating a string of code
setInterval("sum = num1 + num2", 100);

    Whenever you're evaluating JavaScript code from within JavaScript code, you incur a double evaluation penalty. This code is first evaluated as normal, and then, while executing, another evaluation happens to execute the code contained in a string. Double evaluation is a costly operation and takes much longer than if the same code were included natively.

    当你在JavaScript代码中执行(另一段)JavaScript代码时,你付出二次评估的代价。此代码首先被评估为正常代码,然后在执行过程中,运行字符串中的代码时发生另一次评估。二次评估是一项昂贵的操作,与直接包含相应代码相比将占用更长时间。

 

    As a point of comparison, the time it takes to access an array item varies from browser to browser but varies far more dramatically when the array item is accessed using eval(). For example:

    作为一个比较点,不同浏览器上访问一个数组项所占用的时间各有不同,但如果使用eval()访问其结果将大相径庭。例如:

 

//faster
var item = array[0];
//slower
var item = eval("array[0]");

    The difference across browsers becomes dramatic if 10,000 array items are read using eval() instead of native code. Table 8-1 shows the different times for this operation.

    如果使用eval()代替直接代码访问10'000个数组项,在不同浏览器上的差异非常巨大。表8-1显示了这些操作所用的时间。

 

Table 8-1. Speed comparison of native code versus eval() for accessing 10,000 array items

表8-1  直接代码与eval()访问10'000个数组项的速度比较

    This dramatic difference in array item access time is due to the creation of a new interpreter/compiler instance each time eval() is called. The same process occurs for Function(), setTimeout(), and setInterval(), automatically making code execution slower.

    访问数组项时间上的巨大差异,是因为每次调用eval()时要创建一个新的解释/编译实例。同样的过程也发生在Function(),setTimeout()和setInterval()上,自动使代码执行速度变慢。

 

    Most of the time, there is no need to use eval() or Function(), and it's best to avoid them whenever possible. For the other two functions, setTimeout() and setInterval(), it's recommended to pass in a function as the first argument instead of a string. For example:

    大多数情况下,没必要使用eval()或Function(),如果可能的话,尽量避免使用它们。至于另外两个函数,setTimeout()和setInterval(),建议第一个参数传入一个函数而不是一个字符串。例如:

 

setTimeout(function(){
  sum = num1 + num2;
}, 100);
setInterval(function(){
  sum = num1 + num2;
}, 100);

 

    Avoiding double evaluation is key to achieving the most optimal JavaScript runtime performance possible.

    避免二次评估是实现最优化的JavaScript运行时性能的关键。

 

Use Object/Array Literals  使用对象/数组直接量

 

    There are multiple ways to create objects and arrays in JavaScript, but nothing is faster than creating object and array literals. Without using literals, typical object creation and assignment looks like this:

    在JavaScript中有多种方法创建对象和数组,但没有什么比创建对象和数组直接量更快了。如果不使用直接量,典型的对象创建和赋值是这样的:

 

//create an object
var myObject = new Object();
myObject.name = "Nicholas";
myObject.count = 50;
myObject.flag = true;
myObject.pointer = null;
//create an array
var myArray = new Array();
myArray[0] = "Nicholas";
myArray[1] = 50;
myArray[2] = true;
myArray[3] = null;

 

    Although there is technically nothing wrong with this approach, literals are evaluated faster. As an added bonus, literals take up less space in your code, so the overall file size is smaller. The previous code can be rewritten using literals in the following way:

    虽然在技术上这种做法没有什么不对,直接量赋值很快。作为一个额外的好处,直接量在你的代码中占用较少空间,所以整个文件尺寸可以更小。上面的代码可用直接量重写为下面的样式:

 

//create an object
var myObject = {
  name: "Nicholas",
  count: 50,
  flag: true,
  pointer: null
};
//create an array
var myArray = ["Nicholas", 50, true, null];

 

    The end result of this code is the same as the previous version, but it is executed faster in almost all browsers (Firefox 3.5 shows almost no difference). As the number of object properties and array items increases, so too does the benefit of using literals.

    此代码的效果与前面的版本相同,但在几乎所有浏览器上运行更快(在Firefox 3.5上几乎没区别)。随着对象属性和数组项数的增加,使用直接量的好处也随之增加。

 

Don't Repeat Work  不要重复工作

 

    One of the primary performance optimization techniques in computer science overall is work avoidance. The concept of work avoidance really means two things: don't do work that isn't required, and don't repeat work that has already been completed. The first part is usually easy to identify as code is being refactored. The second part—not repeating work—is usually more difficult to identify because work may be repeated in any number of places and for any number of reasons.

    在计算机科学领域最重要的性能优化技术之一是避免工作。避免工作的概念实际上意味着两件事:不要做不必要的工作,不要重复做已经完成的工作。第一部分通常认为代码应当重构。第二部分——不要重复工作——通常难以确定,因为工作可能因为各种原因而在很多地方被重复。

 

    Perhaps the most common type of repeated work is browser detection. A lot of code has forks based on the browser's capabilities. Consider event handler addition and removal as an example. Typical cross-browser code for this purpose looks like the following:

    也许最常见的重复工作类型是浏览器检测。大量代码依赖于浏览器的功能。以事件句柄的添加和删除为例,典型的跨浏览器代码如下:

 

function addHandler(target, eventType, handler){
  if (target.addEventListener){ //DOM2 Events
    target.addEventListener(eventType, handler, false);
  } else { //IE
    target.attachEvent("on" + eventType, handler);
  }
}
function removeHandler(target, eventType, handler){
  if (target.removeEventListener){ //DOM2 Events
    target.removeEventListener(eventType, handler, false);
  } else { //IE
    target.detachEvent("on" + eventType, handler);
  }
}

    The code checks for DOM Level 2 Events support by testing for addEventListener() and removeEventListener(), which is supported by all modern browsers except Internet Explorer. If these methods don't exist on the target, then IE is assumed and the IE-specific methods are used.

    此代码通过测试addEventListener()和removeEventListener()检查DOM级别2的事件支持情况,它能够被除Internet Explorer之外的所有现代浏览器所支持。如果这些方法不存在于target中,那么就认为当前浏览器是IE,并使用IE特有的方法。

 

    At first glance, these functions look fairly optimized for their purpose. The hidden performance issue is in the repeated work done each time either function is called. Each time, the same check is made to see whether a certain method is present. If you assume that the only values for target are actually DOM objects, and that the user doesn't magically change his browser while the page is loaded, then this evaluation is repetitive. If addEventListener() was present on the first call to addHandler() then it's going to be present for each subsequent call. Repeating the same work with every call to a function is wasteful, and there are a couple of ways to avoid it.

    乍一看,这些函数为实现它们的目的已经足够优化。隐藏的性能问题在于每次函数调用时都执行重复工作。每一次,都进行同样的检查,看看某种方法是否存在。如果你假设target唯一的值就是DOM对象,而且用户不可能在页面加载时魔术般地改变浏览器,那么这种判断就是重复的。如果addHandler()一上来就调用addEventListener()那么每个后续调用都要出现这句代码。在每次调用中重复同样的工作是一种浪费,有多种办法避免这一点。

 

Lazy Loading  延迟加载

 

    The first way to eliminate work repetition in functions is through lazy loading. Lazy loading means that no work is done until the information is necessary. In the case of the previous example, there is no need to determine which way to attach or detach event handlers until someone makes a call to the function. Lazy-loaded versions of the previous functions look like this:

    第一种消除函数中重复工作的方法称作延迟加载。延迟加载意味着在信息被使用之前不做任何工作。在前面的例子中,不需要判断使用哪种方法附加或分离事件句柄,直到有人调用此函数。使用延迟加载的函数如下:

 

function addHandler(target, eventType, handler){
  //overwrite the existing function
  if (target.addEventListener){ //DOM2 Events
    addHandler = function(target, eventType, handler){
      target.addEventListener(eventType, handler, false);
    };
  } else { //IE
    addHandler = function(target, eventType, handler){
      target.attachEvent("on" + eventType, handler);
    };
  }
  //call the new function
  addHandler(target, eventType, handler);
}
function removeHandler(target, eventType, handler){
  //overwrite the existing function
  if (target.removeEventListener){ //DOM2 Events
    removeHandler = function(target, eventType, handler){
      target.addEventListener(eventType, handler, false);
    };
  } else { //IE
    removeHandler = function(target, eventType, handler){
      target.detachEvent("on" + eventType, handler);
    };
  }
  //call the new function
  removeHandler(target, eventType, handler);
}

    These two functions implement a lazy-loading pattern. The first time either method is called, a check is made to determine the appropriate way to attach or detach the event handler. Then, the original function is overwritten with a new function that contains just the appropriate course of action. The last step during that first function call is to execute the new function with the original arguments. Each subsequent call to addHandler() or removeHandler() avoids further detection because the detection code was overwritten by a new function.

    这两个函数依照延迟加载模式实现。这两个方法第一次被调用时,检查一次并决定使用哪种方法附加或分离事件句柄。然后,原始函数就被包含适当操作的新函数覆盖了。最后调用新函数并将原始参数传给它。以后再调用addHandler()或者removeHandler()时不会再次检测,因为检测代码已经被新函数覆盖了。

 

    Calling a lazy-loading function always takes longer the first time because it must run the detection and then make a call to another function to accomplish the task. Subsequent calls to the same function, however, are much faster since they have no detection logic. Lazy loading is best used when the function won't be used immediately on the page.

    调用一个延迟加载函数总是在第一次使用较长时间,因为它必须运行检测然后调用另一个函数以完成任务。但是,后续调用同一函数将快很多,因为不再执行检测逻辑了。延迟加载适用于函数不会在页面上立即被用到的场合。

 

Conditional Advance Loading  条件预加载

 

    An alternative to lazy-loading functions is conditional advance loading, which does the detection upfront, while the script is loading, instead of waiting for the function call. The detection is still done just once, but it comes earlier in the process. For example:

    除延迟加载之外的另一种方法称为条件预加载,它在脚本加载之前提前进行检查,而不等待函数调用。这样做检测仍只是一次,但在此过程中来的更早。例如:

 

var addHandler = document.body.addEventListener ?
  function(target, eventType, handler){
    target.addEventListener(eventType, handler, false);
  }:
  function(target, eventType, handler){
    target.attachEvent("on" + eventType, handler);
  };
var removeHandler = document.body.removeEventListener ?
  function(target, eventType, handler){
    target.removeEventListener(eventType, handler, false);
  }:
  function(target, eventType, handler){
    target.detachEvent("on" + eventType, handler);
  };

    This example checks to see whether addEventListener() and removeEventListener() are present and then uses that information to assign the most appropriate function. The ternary operator returns the DOM Level 2 function if these methods are present and otherwise returns the IE-specific function. The result is that all calls to addHandler() and removeHandler() are equally fast, as the detection cost occurs upfront.

    这个例子检查addEventListener()和removeEventListener()是否存在,然后根据此信息指定最合适的函数。三元操作符返回DOM级别2的函数,如果它们存在的话,否则返回IE特有的函数。然后,调用addHandler()和removeHandler()同样很快,虽然检测功能提前了。

 

    Conditional advance loading ensures that all calls to the function take the same amount of time. The trade-off is that the detection occurs as the script is loading rather than later. Advance loading is best to use when a function is going to be used right away and then again frequently throughout the lifetime of the page.

    条件预加载确保所有函数调用时间相同。其代价是在脚本加载时进行检测。预加载适用于一个函数马上就会被用到,而且在整个页面生命周期中经常使用的场合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值