`
wenbois2000
  • 浏览: 44669 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
社区版块
存档分类
最新评论

High Performance JavaScript 读书笔记(六)

    博客分类:
  • Web
阅读更多

六.Responsive Interfaces
   There’s nothing more frustrating than clicking something on a web page and having nothing happen. This problem goes back to the origin of transactional web applications and resulted in the now-ubiquitous “please click only once” message that accompanies most form submissions. A user’s natural inclination is to repeat any action that doesn’t result in an obvious change, and so ensuring responsiveness in web applications is an important performance concern.
  Chapter 1 introduced the browser UI thread concept. As a recap, most browsers have a single process that is shared between JavaScript execution and user interface updates. Only one of these operations can be performed at a time, meaning that the user interface cannot respond to input while JavaScript code is executed and vice versa. The user interface effectively becomes “locked” when JavaScript is executing; managing how long your JavaScript takes to execute is important to the perceived performance of a web application.

  • The Browser UI Thread
      The process shared by JavaScript and user interface updates is frequently referred to as the browser UI thread (though the term “thread” is not necessarily accurate for all browsers). The UI thread works on a simple queuing system where tasks are kept until the process is idle. Once idle, the next task in the queue is retrieved and executed. These tasks are either JavaScript code to execute or UI updates to perform, which include redraws and reflows (discussed in Chapter 3). Perhaps the most interesting part of this process is that each input may result in one or more tasks being added to the queue. Consider a simple interface where a button click results in a message being displayed on the screen:
      Consider a simple interface where a button click results in a message being displayed on the screen:
    <html>
    	<head>
    		<title>Browser UI Thread Example</title>
    	</head>
    	<body>
    		<button onclick="handleClick()">Click Me</button>
    		<script type="text/javascript">
    			function handleClick(){
    				var div = document.createElement("div");
    				div.innerHTML = "Clicked!";
    				document.body.appendChild(div);
    			}
    		</script >
    	</body>
    </html>
    
       When the button in this example is clicked, it triggers the UI thread to create and add two tasks to the queue. The first task is a UI update for the button, which needs to change appearance to indicate it was clicked, and the second is a JavaScript execution task containing the code for handleClick(), so that the only code being executed is this method and anything it calls. Assuming the UI thread is idle, the first task is retrieved and executed to update the button’s appearance, and then the JavaScript task is retrieved and executed. During the course of execution, handleClick() creates a new <div> element and appends it to the <body> element, effectively making another UI change. That means that during the JavaScript execution, a new UI update task is added to the queue such that the UI is updated once JavaScript execution is complete.


     
      When all UI thread tasks have been executed, the process becomes idle and waits for more tasks to be added to the queue. The idle state is ideal because all user actions then result in an immediate UI update. If the user tries to interact with the page while a task is being executed, not only will there not be an immediate UI update, but a new task for a UI update may not even be created and queued. In fact, most browsers stop queuing tasks for the UI thread while JavaScript is executing, which means that it is imperative to finish JavaScript tasks as quickly as possible so as not to adversely affect the user’s experience.

    a.Browser Limits
       Browsers place limits on the amount of time that JavaScript take to execute. This is a necessary limitation to ensure that malicious coders can’t lock up a user’s browser or computer by performing intensive operations that will never end. There are two such limits: the call stack size limit (discussed in Chapter 4) and the long-running script limit. The long-running script limit is sometimes called the long-running script timer or the runaway script timer, but the basic idea is that the browser keeps track of how long a script has been running and will stop it once a certain limit is hit. When the limit is reached, a dialog is displayed to the user, such as the one in fellow.  
        
                          
     
       There are two ways of measuring how long a script is executing. The first is to keep track of how many statements have been executed since the script began. This approach means that the script may run for different periods of time on different machines, as the available memory and CPU speed can affect how long it takes to execute a single statement. The second approach is to track the total amount of time that the script has been executing. The amount of script that can be processed within a set amount of time also varies based on the user’s machine capabilities, but the script is always stopped after a set amount of time. Not surprisingly, each browser has a slightly different approach to long-running script detection:

     • Internet Explorer, as of version 4, sets a default limit of 5 million statements; this limit is stored in a Windows registry setting called HKEY_CURRENT_USER\Software\Microsoft\InternetExplorer\Styles\MaxScriptStatements.

    • Firefox has a default limit of 10 seconds; this limit is stored in the browser’s configuration settings (accessible by typing about:config in the address box) as the dom.max_script_run_time key.

    • Safari has a default limit of 5 seconds; this setting cannot be altered, but you can disable the timer by enabling the Develop menu and selecting Disable Runaway JavaScript Timer.

    • Chrome has no separate long-running script limit and instead relies on its generic crash detection system to handle such instances.

    • Opera has no long-running script limit and will continue to execute JavaScript code until it has finished, though, due to Opera’s architecture, this will not cause system instability while the execution is completed.

        When the browser’s long-running script limit is reached, a dialog is displayed to the user, regardless of any other error-handling code on the page. This is a major usability issue because most Internet users are not technically savvy and would therefore be confused about the meaning of the error message as well as which option (to stop the script or allow it to continue) is appropriate.
      If your script triggers this dialog in any browser, it means the script is simply taking too long to complete its task. It also indicates that the user’s browser has become unresponsive to input while the JavaScript code is continuing to execute. From a developer’s point of view, there is no way to recover from a long-running script dialog’s appearance; you can’t detect it and therefore can’t adjust to any issues that might arise as a result. Clearly, the best way to deal with long-running script limits is to avoid them in the first place.

    b.How Long Is Too Long?
      Just because the browser allows a script to continue executing up to a certain number of seconds doesn’t mean you should allow it do so. In fact, the amount of time that your JavaScript code executes continuously should be much smaller than the browserimposed limits in order to create a good user experience. Brendan Eich, creator of JavaScript, is quoted as having once said, “[JavaScript] that executes in whole seconds is probably doing something wrong….”
       If whole seconds are too long for JavaScript to execute, what is an appropriate amount of time? As it turns out, even one second is too long for a script to execute. The total amount of time that a single JavaScript operation should take (at a maximum) is 100 milliseconds. This number comes from research conducted by Robert Miller in 1968.* Interestingly, usability expert Jakob Nielsen noted† in his book Usability  Engineering (Morgan Kaufmann, 1994) that this number hasn’t changed over time and, in fact, was reaffirmed in 1991 by research at Xerox-PARC.‡
       Nielsen states that if the interface responds to user input within 100 milliseconds, the user feels that he is “directly manipulating the objects in the user interface.” Any amount of time more than 100 milliseconds means the user feels disconnected from the interface. Since the UI cannot update while JavaScript is executing, the user cannot feel in control of the interface if that execution takes longer than 100 milliseconds. A further complication is that some browsers won’t even queue UI updates while JavaScript is executing. For example, if you click a button while some JavaScript code is executing, the browser may not queue up the UI update to redraw the button as pressed or any JavaScript initiated by the button. The result is an unresponsive UI that appears to “hang” or “freeze.”
       Each browser behaves in roughly the same way. When a script is executing, the UI does not update from user interaction. JavaScript tasks created as a result of user interaction during this time are queued and then executed, in order, when the original JavaScript task has been completed. UI updates caused by user interaction are automatically skipped over at this time because the priority is given to the dynamic aspects of the page.
       Thus, a button clicked while a script is executing will never look like it was clicked, even though its onclick handler will be executed.  Internet Explorer throttles JavaScript tasks triggered by user interaction so that it  recognizes only two repeated actions in a row. For example, clicking on a button four times while a script is executing results in the onclick event handler being called only twice.
       Even though browsers try to do something logical in these cases, all of these behaviors lead to a disjointed user experience. The best approach, therefore, is to prevent such circumstances from occurring by limiting any JavaScript task to 100 milliseconds or less. This measurement should be taken on the slowest browser you must support.

  • 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.

    1.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.
       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:
    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:
    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. Follow diagram shows this relationship when the button in this example is clicked.


     
       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:
    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.


     
       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. 
      The setInterval() function is almost the same as setTimeout(), except that the former sepeatedly adds JavaScript tasks into the UI queue. The main difference is that it will not add a JavaScript task into the UI queue if a task created by the same setInterval() call is already present in the UI queue.

    2.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.
      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. 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.

    3.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:

        • 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.
      The actual amount of time to delay each timer is largely dependent on your use case. Generally speaking, it’s best to use at least 25 milliseconds because smaller delays leave too little time for most UI updates.
      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:
    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.
       One side effect of using timers to process arrays is that the total time to process the array increases. This is because the UI thread is freed up after each item is processed and there is a delay before the next item is processed. Nevertheless, this is a necessary trade-off to avoid a poor user experience by locking up the browser.

    4.Splitting Up Tasks
       What we typically think of as one task can often be broken down into a series of subtasks.If a single function is taking too long to execute, check to see whether it can be broken down into a series of smaller functions that complete in smaller amounts of time. This is often as simple as considering a single line of code as an atomic task, even though multiple lines of code typically can be grouped together into a single task. Some functions are already easily broken down based on the other functions they call. For example:
    function saveDocument(id){
    	//save the document
    	openDocument(id)
    	writeText(id);
    	closeDocument(id);
    	//update the UI to indicate success
    	updateUI(id);
    }
    
       If this function is taking too long, it can easily be split up into a series of smaller steps by breaking out the individual methods into separate timers. You can accomplish this by adding each function into an array and then using a pattern similar to the arrayprocessing pattern from the previous section:
    function saveDocument(id){
    	var tasks = [openDocument, writeText, closeDocument, updateUI];
    	setTimeout(function(){
    		//execute the next task
    		var task = tasks.shift();
    		task(id);
    
    		//determine if there's more
    		if (tasks.length > 0){
    			setTimeout(arguments.callee, 25);
    		}
    	}, 25);
    }
    
       This version of the function places each method into the tasks array and then executes only one method with each timer. Fundamentally, this now becomes an arrayprocessing pattern, with the sole difference that processing an item involves executing the function contained in the item. As discussed in the previous section, this pattern can be encapsulated for reuse:
    function multistep(steps, args, callback){
    	var tasks = steps.concat(); //clone the array
    	setTimeout(function(){
    		//execute the next task
    		var task = tasks.shift();
    		task.apply(null, args || []);
    
    		//determine if there's more
    		if (tasks.length > 0){
    			setTimeout(arguments.callee, 25);
    		} else {
    			callback();
    		}
    	}, 25);
    }
    
       The multistep() function accepts three arguments: an array of functions to execute,an array of arguments to pass into each function when it executes, and a callback function to call when the process is complete. This function can be used like the following:
    function saveDocument(id){
    	var tasks = [openDocument, writeText, closeDocument, updateUI];
    	multistep(tasks, [id], function(){
    		alert("Save completed!");
    	});
    }
    
       Note that the second argument to multistep() must be an array, so one is created containing just id. As with array processing, this function is best used when the tasks can be processed asynchronously without affecting the user experience or causing errors in dependent code.

    5.Timed Code
       Sometimes executing just one task at a time is inefficient. Consider processing an array of 1,000 items for which processing a single item takes 1 millisecond. If one item is processed in each timer and there is a delay of 25 milliseconds in between, that means the total amount of time to process the array is (25 + 1) × 1,000 = 26,000 milliseconds, or 26 seconds. What if you processed the items in batches of 50 with a 25-millisecond delay between them? The entire processing time then becomes (1,000 / 50) × 25 + 1,000 = 1,500 milliseconds, or 1.5 seconds, and the user is still never blocked from the interface because the longest the script has executed continuously is 50 milliseconds. It’s typically faster to process items in batches than one at a time.  If you keep 100 milliseconds in mind as the absolute maximum amount of time that JavaScript should be allowed to run continuously, then you can start optimizing the previous patterns. My recommendation is to cut that number in half and never let any JavaScript code execute for longer than 50 milliseconds continuously, just to make sure the code never gets close to affecting the user experience.
      It’s possible to track how long a piece of code has been running by using the native Date object. This is the way most JavaScript profiling works:
    var start = +new Date(),
    stop;
    
    someLongProcess();
    
    stop = +new Date();
    if(stop-start < 50){
    	alert("Just about right.");
    } else {
    	alert("Taking too long.");
    }
    
       Since each new Date object is initialized with the current system time, you can time code by creating new Date objects periodically and comparing their values. The plus operator (+) converts the Date object into a numeric representation so that any further arithmetic doesn’t involve conversions. This same basic technique can be used to optimize the previous timer patterns.
      The processArray() method can be augmented to process multiple items per timer by adding in a time check:
    function timedProcessArray(items, process, callback){
    	var todo = items.concat(); //create a clone of the original
    	setTimeout(function(){
    		var start = +new Date();
    
    		do {
    			process(todo.shift());
    		} while (todo.length > 0 && (+new Date() - start < 50));
    
    		if (todo.length > 0){
    			setTimeout(arguments.callee, 25);
    		} else {
    			callback(items);
    		}
    	}, 25);
    }
    
       The addition of a do-while loop in this function enables checking the time after each item is processed. The array will always contain at least one item when the timer function executes, so a post-test loop makes more sense than a pretest one. When run in Firefox 3, this function processes an array of 1,000 items, where process() is an empty function, in 38–43 milliseconds; the original processArray() function processes the same array in over 25,000 milliseconds. This is the power of timing tasks before breaking them up into smaller chunks.

    6.Timers and Performance
       Timers can make a huge difference in the overall performance of your JavaScript code, but overusing them can have a negative effect on performance. The code in this section has used sequenced timers such that only one timer exists at a time and new ones are created only when the last timer has finished. Using timers in this way will not result in performance issues.
       Performance issues start to appear when multiple repeating timers are being created at the same time. Since there is only one UI thread, all of the timers compete for time to execute. Neil Thomas of Google Mobile researched this topic as a way of measuring performance on the mobile Gmail application for the iPhone and Android.§
      Thomas found that low-frequency repeating timers—those occurring at intervals of one second or greater—had little effect on overall web application responsiveness. The timer delays in this case are too large to create a bottleneck on the UI thread and are therefore safe to use repeatedly. When multiple repeating timers are used with a much greater frequency (between 100 and 200 milliseconds), however, Thomas found that the mobile Gmail application became noticeably slower and less responsive. The takeaway from Thomas’s research is to limit the number of high-frequency repeating timers in your web application. Instead, Thomas suggests creating a single repeating timer that performs multiple operations with each execution.


  • Web Workers
       Since JavaScript was introduced, there has been no way to execute code outside of the browser UI thread. The web workers API changes this by introducing an interface through which code can be executed without taking time on the browser UI thread. Originally part of HTML 5, the web workers API has been split out into its own specification (http://www.w3.org/TR/workers/); web workers have already been implemented natively in Firefox 3.5, Chrome 3, and Safari 4.
       Web workers represent a potentially huge performance improvement for web applications because each new worker spawns its own thread in which to execute JavaScript. That means not only will code executing in a worker not affect the browser UI, but it also won’t affect code executing in other workers.

    1.Worker Environment
       Since web workers aren’t bound to the UI thread, it also means that they cannot access a lot of browser resources. Part of the reason that JavaScript and UI updates share the same process is because one can affect the other quite frequently, and so executing these tasks out of order results in a bad user experience. Web workers could introduce user interface errors by making changes to the DOM from an outside thread, but each web worker has its own global environment that has only a subset of JavaScript features available. The worker environment is made up of the following:

          • A navigator object, which contains only four properties: appName, appVersion, user Agent, and platform

          • A location object (same as on window, except all properties are read-only)

          • A self object that points to the global worker object

          • An importScripts() method that is used to load external JavaScript for use in the worker

          • All ECMAScript objects, such as Object, Array, Date, etc.

          • The XMLHttpRequest constructor

          • The setTimeout() and setInterval() methods

          • A close() method that stops the worker immediately

       Because web workers have a different global environment, you can’t create one from any JavaScript code. In fact, you’ll need to create an entirely separate JavaScript file containing just the code for the worker to execute. To create a web worker, you must pass in the URL for the JavaScript file:
    var worker = new Worker("code.js");
       Once this is executed, a new thread with a new worker environment is created for the specified file. This file is downloaded asynchronously, and the worker will not begin until the file has been completely downloaded and executed.

    2.Worker Communication
       Communication between a worker and the web page code is established through an event interface. The web page code can pass data to the worker via the postMessage() method, which accepts a single argument indicating the data to pass into the worker. There is also an onmessage event handler that is used to receive information from the worker. For example:
    var worker = new Worker("code.js");
    worker.onmessage = function(event){
    	alert(event.data);
    };
    worker.postMessage("Nicholas");
    
       The worker receives this data through the firing of a message event. An onmessage event handler is defined, and the event object has a data property containing the data that was passed in. The worker can then pass information back to the web page by using its own postMessage() method:
    //inside code.js
    self.onmessage = function(event){
    	self.postMessage("Hello, " + event.data + "!");
    };
    
       The final string ends up in the onmessage event handler for the worker. This messaging system is the only way in which the web page and the worker can communicate.  Only certain types of data can be passed using postMessage(). You can pass primitive values (strings, numbers, Booleans, null, and undefined) as well as instances of Object and Array; you cannot pass any other data types. Valid data is serialized, transmitted to or from the worker, and then deserialized. Even though it seems like the objects are being passed through directly, the instances are completely separate representations of the same data. Attempting to pass an unsupported data type results in a JavaScript error.
       Safari 4's implementation of workers only allows you to pass strings using postMessage(). The specification was updated after that point to allow serializable data to be passed through, which is how Firefox 3.5 implements workers.

    3.Loading External Files
       Loading extra JavaScript files into a worker is done via the importScripts() method, which accepts one or more URLs for JavaScript files to load. The call to importScripts() is blocking within the worker, so the script won’t continue until all files have been loaded and executed. Since the worker is running outside of the UI thread, there is no concern about UI responsiveness when this blocking occurs. For example:
    //inside code.js
    importScripts("file1.js", "file2.js");
    self.onmessage = function(event){
    	self.postMessage("Hello, " + event.data + "!");
    };
    
       The first line in this code includes two JavaScript files so that they will be available in the context of the worker.

    4.Practical Uses
       Web workers are suitable for any long-running scripts that work on pure data and that have no ties to the browser UI. This may seem like a fairly small number of uses, but buried in web applications there are typically some data-handling approaches that would benefit from using a worker instead of timers.
       Consider, for example, parsing a large JSON string. Suppose that the data is large enough that parsing takes at least 500 milliseconds. That is clearly too long to allow JavaScript to run on the client, as it will interfere with the user experience. This particular task is difficult to break into small chunks with timers, so a worker is the ideal solution. The following code illustrates usage from a web page:
    var worker = new Worker("jsonparser.js");
    //when the data is available, this event handler is called
    worker.onmessage = function(event){
    	//the JSON structure is passed back
    	var jsonData = event.data;
    	//the JSON structure is used
    	evaluateData(jsonData);
    };
    //pass in the large JSON string to parse
    worker.postMessage(jsonText);
    
     The code for the worker responsible for JSON parsing is as follows:
    //inside of jsonparser.js
    //this event handler is called when JSON data is available
    self.onmessage = function(event){
    	//the JSON string comes in as event.data
    	var jsonText = event.data;
    	//parse the structure
    	var jsonData = JSON.parse(jsonText);
    	//send back to the results
    	self.postMessage(jsonData);
    };
    
       Note that even though JSON.parse() is likely to take 500 milliseconds or more, there is no need to write any additional code to split up the processing. This execution takes place on a separate thread, so you can let it run for as long as the parsing takes without interfering with the user experience.
       The page passes a JSON string into the worker by using postMessage(). The worker receives the string as event.data in its onmessage event handler and then proceeds to parse it. When complete, the resulting JSON object is passed back to the page using the worker’s postMessage() method. This object is then available as event.data in the page’s onmessage event handler. Keep in mind that this presently works only in Firefox 3.5 and later, as Safari 4 and Chrome 3’s implementations allow strings to be passed only between page and worker.
       Parsing a large string is just one of many possible tasks that can benefit from web workers. Some other possibilities are:

       • Encoding/decoding a large string
       • Complex mathematical calculations (including image or video processing)
       • Sorting a large array

       Any time a process takes longer than 100 milliseconds to complete, you should consider whether a worker solution is more appropriate than a timer-based one. This, of course, is based on browser capabilities.

Summary
    JavaScript and user interface updates operate within the same process, so only one can be done at a time. This means that the user interface cannot react to input while JavaScript code is executing and vice versa. Managing the UI thread effectively means ensuring that JavaScript isn’t allowed to run so long that the user experience is affected. To that end, the following should be kept in mind:
 

  • No JavaScript task should take longer than 100 milliseconds to execute. Longer execution times cause a noticeable delay in updates to the UI and negatively impact the overall user experience.
  • Browsers behave differently in response to user interaction during JavaScript execution. Regardless of the behavior, the user experience becomes confusing and disjointed when JavaScript takes a long time to execute.
  • Timers can be used to schedule code for later execution, which allows you to split up long-running scripts into a series of smaller tasks.
  • Web workers are a feature in newer browsers that allow you to execute JavaScript code outside of the UI thread, thus preventing UI locking.

    The more complex the web application, the more critical it is to manage the UI thread  in a proactive manner. No JavaScript code is so important that it should adversely affect the user’s experience.

  • 大小: 185.5 KB
  • 大小: 94 KB
  • 大小: 205.2 KB
  • 大小: 200.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics