[翻译]High Performance JavaScript(020)

来源:互联网 发布:js时间范围选择插件 编辑:程序博客网 时间:2024/05/20 09:49

Yielding with Timers  用定时器让出时间片

 

    Despite your best efforts, there will be times when a JavaScript task cannot be completed in 100 milliseconds or less because of its complexity. In these cases, it's ideal to yield control of the UI thread so that UI updates may occur. Yielding control means stopping JavaScript execution and giving the UI a chance to update itself before continuing to execute the JavaScript. This is where JavaScript timers come into the picture.

    尽管你尽了最大努力,还是有一些JavaScript任务因为复杂性原因不能在100毫秒或更少时间内完成。这种情况下,理想方法是让出对UI线程的控制,使UI更新可以进行。让出控制意味着停止JavaScript运行,给UI线程机会进行更新,然后再继续运行JavaScript。于是JavaScript定时器进入了我们的视野。

 

Timer Basics  定时器基础

 

    Timers are created in JavaScript using either setTimeout() or setInterval(), and both accept the same arguments: a function to execute and the amount of time to wait (in milliseconds) before executing it. The setTimeout() function creates a timer that executes just once, whereas the setInterval() function creates a timer that repeats periodically.

    在JavaScript中使用setTimeout()或setInterval()创建定时器,两个函数都接收一样的参数:一个要执行的函数,和一个运行它之前的等待时间(单位毫秒)。setTimeout()函数创建一个定时器只运行一次,而setInterval()函数创建一个周期性重复运行的定时器。

 

    The way that timers interact with the UI thread is helpful for breaking up long-running scripts into shorter segments. Calling setTimeout() or setInterval() tells the JavaScript engine to wait a certain amount of time and then add a JavaScript task to the UI queue. For example:

    定时器与UI线程交互的方式有助于分解长运行脚本成为较短的片断。调用setTimeout()或setInterval()告诉JavaScript引擎等待一定时间然后将JavaScript任务添加到UI队列中。例如:

 

function greeting(){
  alert("Hello world!");
}
setTimeout(greeting, 250);

    This code inserts a JavaScript task to execute the greeting() function into the UI queue after 250 milliseconds have passed. Prior to that point, all other UI updates and JavaScript tasks are executed. Keep in mind that the second argument indicates when the task should be added to the UI queue, which is not necessarily the time that it will be executed; the task must wait until all other tasks already in the queue are executed, just like any other task. Consider the following:

    此代码将在250毫秒之后,向UI队列插入一个JavaScript任务运行greeting()函数。在那个点之前,所有其他UI更新和JavaScript任务都在运行。请记住,第二个参数指出什么时候应当将任务添加到UI队列之中,并不是说那时代码将被执行。这个任务必须等到队列中的其他任务都执行之后才能被执行。考虑下面的例子:

 

var button = document.getElementById("my-button");
button.onclick = function(){
  oneMethod();
  setTimeout(function(){
    document.getElementById("notice").style.color = "red";
  }, 250);
};

    When the button in this example is clicked, it calls a method and then sets a timer. The code to change the notice element's color is contained in a timer set to be queued in 250 milliseconds. That 250 milliseconds starts from the time at which setTimeout() is called, not when the overall function has finished executing. So if setTimeout() is called at a point in time n, then the JavaScript task to execute the timer code is added to the UI queue at n + 250. Figure 6-3 shows this relationship when the button in this example is clicked.

    在这个例子中当按钮被点击时,它调用一个方法然后设置一个定时器。用于修改notice元素颜色的代码被包含在一个定时器设备中,将在250毫秒之后添加到队列。250毫秒从调用setTimeout()时开始计算,而不是从整个函数运行结束时开始计算。如果setTimeout()在时间点n上被调用,那么运行定时器代码的JavaScript任务将在n+250的时刻加入UI队列。图6-3显示出本例中按钮被点击时所发生事件之间的关系。

Figure 6-3. The second argument of setTimeout() indicates when the new JavaScript task should be
inserted into the UI queue

图6-3  setTimeout()的第二个参数指出何时将新的JavaScript任务插入到UI队列中

 

    Keep in mind that the timer code can never be executed until after the function in which it was created is completely executed. For example, if the previous code is changed such that the timer delay is smaller and there is another function call after the timer is created, it's possible that the timer code will be queued before the onclick event handler has finished executing:

    请记住,定时器代码只有等创建它的函数运行完成之后,才有可能被执行。例如,如果前面的代码中定时器延时变得更小,然后在创建定时器之后又调用了另一个函数,定时器代码有可能在onclick事件处理完成之前加入队列:

 

var button = document.getElementById("my-button");
button.onclick = function(){
  oneMethod();
  setTimeout(function(){
    document.getElementById("notice").style.color = "red";
  }, 50);
  anotherMethod();
};
 

    If anotherMethod() takes longer than 50 milliseconds to execute, then the timer code is added to the queue before the onclick handler is finished. The effect is that the timer code executes almost immediately after the onclick handler has executed completely, without a noticeable delay. Figure 6-4 illustrates this situation.

    如果anotherMethod()执行时间超过50毫秒,那么定时器代码将在onclick处理完成之前加入到队列中。其结果是等onclick处理运行完毕,定时器代码立即执行,而察觉不出其间的延迟。图6-4说明了这种情况。

 

    In either case, creating a timer creates a pause in the UI thread as it switches from one task to the next. Consequently, timer code resets all of the relevant browser limits, including the long-running script timer. Further, the call stack is reset to zero inside of the timer code. These characteristics make timers the ideal cross-browser solution for long-running JavaScript code.

    在任何一种情况下,创建一个定时器造成UI线程暂停,如同它从一个任务切换到下一个任务。因此,定时器代码复位所有相关的浏览器限制,包括长运行脚本时间。此外,调用栈也在定时器代码中复位为零。这一特性使得定时器成为长运行JavaScript代码理想的跨浏览器解决方案。

Figure 6-4. There may be no noticeable delay in timer code execution if the function in which
setTimeout() is called takes longer to execute than the timer delay

图6-4  如果调用setTimeout()的函数又调用了其他任务,耗时超过定时器延时,定时器代码将立即被执行,它与主调函数之间没有可察觉的延迟

 

Timer Precision  定时器精度

 

    JavaScript timer delays are often imprecise, with slips of a few milliseconds in either direction. Just because you specify 250 milliseconds as the timer delay doesn't necessarily mean the task is queued exactly 250 milliseconds after setTimeout() is called. All browsers make an attempt to be as accurate as possible, but oftentimes a slip of a few milliseconds in either direction occurs. For this reason, timers are unreliable for measuring actual time passed.

    JavaScript定时器延时往往不准确,快慢大约几毫秒。仅仅因为你指定定时器延时250毫秒,并不意味任务将在调用setTimeout()之后精确的250毫秒后加入队列。所有浏览器试图尽可能准确,但通常会发生几毫秒滑移,或快或慢。正因为这个原因,定时器不可用于测量实际时间。

 

    Timer resolution on Windows systems is 15 milliseconds, meaning that it will interpret a timer delay of 15 as either 0 or 15, depending on when the system time was last updated. Setting timer delays of less than 15 can cause browser locking in Internet Explorer, so the smallest recommended delay is 25 milliseconds (which will end up as either 15 or 30) to ensure a delay of at least 15 milliseconds.

    在Windows系统上定时器分辨率为15毫秒,也就是说一个值为15的定时器延时将根据最后一次系统时间刷新而转换为0或者15。设置定时器延时小于15将在Internet Explorer中导致浏览器锁定,所以最小值建议为25毫秒(实际时间是15或30)以确保至少15毫秒延迟。

 

    This minimum timer delay also helps to avoid timer resolution issues in other browsers and on other systems. Most browsers show some variance in timer delays when dealing with 10 milliseconds or smaller.

    此最小定时器延时也有助于避免其他浏览器和其他操作系统上的定时器分辨率问题。大多数浏览器在定时器延时小于10毫秒时表现出差异性。

 

Array Processing with Timers  在数组处理中使用定时器

 

    One common cause of long-running scripts is loops that take too long to execute. If you've already tried the loop optimization techniques presented in Chapter 4 but haven't been able to reduce the execution time enough, then timers are your next optimization step. The basic approach is to split up the loop's work into a series of timers.

    一个常见的长运行脚本就是循环占用了太长的运行时间。如果你已经尝试了第四章介绍的循环优化技术,但还是不能缩减足够的运行时间,那么定时器就是你的下一个优化步骤。其基本方法是将循环工作分解到定时器序列中。

 

    Typical loops follow a simple pattern, such as:

    典型的循环模式如下:

 

for (var i=0, len=items.length; i < len; i++){
  process(items[i]);
}

    Loops with this structure can take too long to execute due to the complexity of process(), the size of items, or both. In my book Professional JavaScript for Web Developers, Second Edition (Wrox 2009), I lay out the two determining factors for whether a loop can be done asynchronously using timers:

    这样的循环结构运行时间过长的原因有二,process()的复杂度,items的大小,或两者兼有。在我的藏书《Professional JavaScript for Web Developers》第二版(Wrox 2009)中,列举了是否可用定时器取代循环的两个决定性因素:

 

• Does the processing have to be done synchronously?

  此处理过程必须是同步处理吗?


• Does the data have to be processed sequentially?

  数据必须按顺序处理吗?

 

    If the answer to both of these questions is "no," then the code is a good candidate for using timers to split up the work. A basic pattern for asynchronous code execution is:

    如果这两个回答都是“否”,那么代码将适于使用定时器分解工作。一种基本异步代码模式如下:

 

var todo = items.concat(); //create a clone of the original
setTimeout(function(){
  //get next item in the array and process it
  process(todo.shift());
  //if there's more items to process, create another timer
  if(todo.length > 0){
    setTimeout(arguments.callee, 25);
  } else {
    callback(items);
  }
}, 25);

    The basic idea of this pattern is to create a clone of the original array and use that as a queue of items to process. The first call to setTimeout() creates a timer to process the first item in the array. Calling todo.shift() returns the first item and also removes it from the array. This value is passed into process(). After processing the item, a check is made to determine whether there are more items to process. If there are still items in the todo array, there are more items to process and another timer is created. Because the next timer needs to run the same code as the original, arguments.callee is passed in as the first argument. This value points to the anonymous function in which the code is executing. If there are no further items to process, then a callback() function is called.

    这一模式的基本思想是创建一个原始数组的克隆,将它作为处理对象。第一次调用setTimeout()创建一个定时器处理队列中的第一个项。调用todo.shift()返回它的第一个项然后将它从数组中删除。此值作为参数传给process()。然后,检查是否还有更多项需要处理。如果todo队列中还有内容,那么就再启动一个定时器。因为下个定时器需要运行相同的代码,所以第一个参数传入arguments.callee。此值指向当前正在运行的匿名函数。如果不再有内容需要处理,将调用callback()函数。

 

    Because this pattern requires significantly more code that a regular loop, it's useful to encapsulate this functionality. For example:

    此模式与循环相比需要更多代码,可将此功能封装起来。例如:

 

function processArray(items, process, callback){
  var todo = items.concat(); //create a clone of the original
  setTimeout(function(){
    process(todo.shift());
    if (todo.length > 0){
      setTimeout(arguments.callee, 25);
    } else {
      callback(items);
    }
  }, 25);
}

    The processArray() function implements the previous pattern in a reusable way and accepts three arguments: the array to process, the function to call on each item, and a callback function to execute when processing is complete. This function can be used as follows:

    processArray()函数以一种可重用的方式实现了先前的模板,并接收三个参数:待处理数组,对每个项调用的处理函数,处理结束时执行的回调函数。该函数用法如下:

 

var items = [123, 789, 323, 778, 232, 654, 219, 543, 321, 160];
function outputValue(value){
  console.log(value);
}
processArray(items, outputValue, function(){
  console.log("Done!");
});

    This code uses the processArray() method to output array values to the console and then prints a message when all processing is complete. By encapsulating the timer code inside of a function, it can be reused in multiple places without requiring multiple implementations.

    此代码使用processArray()方法将数组值输出到终端,当所有处理结束时再打印一条消息。通过将定时器代码封装在一个函数里,它可在多处重用而无需多次实现。

原创粉丝点击