Preface
Regardless of whether the current JavaScript code is embedded or in an external link file, the download and rendering of the page must be stopped and wait for the script to be executed. The longer the JavaScript execution process takes, the longer the browser waits for the response to user input. The reason why browsers block when downloading and executing scripts is that scripts may change the namespace of the page or JavaScript, which affects the content of the subsequent pages. A typical example is using document.write() in a page, such as Listing 1
Listing 1 JavaScript code embedded example
<html><head> <title>Source Example</title></head><body> <p> <script type="text/javascript"> document.write("Today is " + (new Date()).toDateString()); </script> </p></body></html>When the browser encounters a <script> tag, the current html page has no way of knowing whether JavaScript will add content to the <p> tag, introduce other elements, or even remove the tag. Therefore, at this time, the browser will stop processing the page, execute JavaScript code first, and then continue to parse and render the page. The same happens when loading JavaScript using the src property. The browser must first take the time to download the code in the external link file, and then parse and execute it. During this process, page rendering and user interaction are completely blocked.
Script Location
The HTML 4 specification states that <script> tags can be placed in <head> or <body> of HTML documents and are allowed to appear multiple times. Web developers are generally accustomed to loading external link JavaScript in <head>, and then using the <link> tag to load external link CSS files or other page information. For example, Listing 2
Listing 2 Example of inefficient script locations
<html><head> <title>Source Example</title> <script type="text/javascript" src="script1.js"></script> <script type="text/javascript" src="script2.js"></script> <script type="text/javascript" src="script2.js"></script> <script type="text/javascript" src="script3.js"></script> <link rel="stylesheet" type="text/css" href="styles.css"></head><body> <p>Hello world!</p></body></html>
However, this conventional approach hides serious performance problems. In the example in Listing 2, when the browser parses the <script> tag (line 4), the browser stops parsing the subsequent content, and prioritizes downloading the script file and executing the code in it, which means that neither the subsequent styles.css style file nor the <body> tag can be loaded. Since the <body> tag cannot be loaded, the page will naturally not be rendered. Therefore, the page is blank until the JavaScript code is fully executed. Figure 1 describes the download process of scripts and style files during page loading.
Figure 1 Loading and execution of JavaScript files blocking download of other files
We can find an interesting phenomenon: the first JavaScript file starts downloading, while blocking the download of other files on the page. In addition, there is a delay before the script1.JS download is completed and script2.js starts downloading, which happens to be the execution process of the script1.js file. Each file must wait until the previous file is downloaded and executed before it can begin downloading. During the download of these files one by one, the user sees a blank page.
Starting with IE 8, Firefox 3.5, Safari 4, and Chrome 2, all allow JavaScript files to be downloaded in parallel. This is good news because the <script> tag does not block other <script> tags when downloading external resources. Unfortunately, the JavaScript download process will still block downloading of other resources, such as style files and images. Although the script download process does not affect each other, the page still has to wait for all JavaScript code to be downloaded and executed before it can continue. So while the latest browsers improve performance by allowing parallel downloads, the problem has not been completely resolved and script blocking remains a problem.
Since scripts block downloads of other resources on the page, it is recommended to place all <script> tags at the bottom of <body> tags as much as possible to minimize the impact on the entire page download. For example, Listing 3
Listing 3 Example of recommended code placement
<html><head> <title>Source Example</title> <link rel="stylesheet" type="text/css" href="styles.css"></head><body> <p>Hello world!</p> <!-- Example of efficient script positioning --> <script type="text/javascript" src="script1.js"></script> <script type="text/javascript" src="script2.js"></script> src="script3.js"></script></body></html>
This code shows the recommended location for placing <script> tags in HTML documents. Although script downloads block another script, most of the page has been downloaded and displayed to the user, so the page download won't appear too slow. This is the first rule for optimizing JavaScript: put the script at the bottom.
Organize scripts
Since each <script> tag is blocked when the page is initially downloaded, reducing the number of <script> tags included on the page helps to improve this situation. This is not only for external link scripts, but also for the number of embedded scripts. Whenever a browser encounters a <script> tag during parsing an HTML page, it will cause a certain delay due to the execution of the script, so minimizing the delay time will significantly improve the overall performance of the page.
This problem is slightly different when dealing with external JavaScript files. Given the additional performance overhead of HTTP requests, downloading a single 100Kb file will be faster than downloading 5 20Kb files. That is, reducing the number of external link scripts in the page will improve performance.
Usually a large website or application needs to rely on several JavaScript files. You can merge multiple files into one, so that you only need to reference a <script> tag to reduce performance consumption. File merging can be achieved through offline packaging tools or some real-time online services.
It should be noted that putting an embedded script after referring to the external link style sheet will cause the page to block and wait for the style sheet to download. This is done to ensure that the embedded scripts can get the most accurate style information when executed. Therefore, it is recommended not to follow the embedded scripts immediately after the <link> tag.
Non-blocking scripts
Reducing JavaScript file size and limiting HTTP requests is not always feasible on feature-rich web applications or large websites. The more features a web application is, the more JavaScript code it needs. Although downloading a single larger JavaScript file only generates an HTTP request once, it will lock the browser for a long time. To avoid this, it is necessary to gradually load JavaScript files into the page through some specific techniques, so that to some extent, it does not block the browser.
The secret to unblocking scripts is that JavaScript code is loaded only after the page is loaded. This means downloading the script after the onload event of the window object is fired. There are a number of ways to achieve this effect.
Delay loading scripts
HTML 4 defines an extended attribute for the <script> tag: defer. The Defer attribute indicates that the script contained in this element does not modify the DOM, so the code can be safely delayed execution. The defer attribute is only supported by IE 4 and Firefox 3.5 later browsers, so it is not an ideal cross-browser solution. In other browsers, the defer attribute is ignored directly, so the <script> tag will be processed in the default way, which means it will cause blockage. However, this is still a useful solution if your target browser supports it. Listing 4 is an example
Listing 4 Example of defer attribute usage method
<script type="text/javascript" src="script1.js" defer></script>
The <script> tag with the defer attribute can be placed anywhere in the document. The corresponding JavaScript file will start downloading when the page is parsed to the <script> tag, but will not be executed until the DOM loads, that is, the onload event will be fired. When a JavaScript file with the defer attribute is downloaded, it does not block other processes in the browser, so such files can be downloaded in parallel with other resource files.
Any <script> element with the defer attribute will not be executed until the DOM is loaded, whether embedded or external scripts. The example in Listing 5 shows how the defer attribute affects script behavior:
Listing 5 The effect of defer attribute on script behavior
<html><head> <title>Script Defer Example</title></head><body> <script type="text/javascript" defer> alert("defer"); </script> <script type="text/javascript"> alert("script"); </script> <script type="text/javascript"> window.onload = function(){ alert("load"); }; </script></body></html>This code pops up three dialog boxes during page processing. The pop-up order of browsers that do not support the defer attribute is: "defer", "script", and "load". On browsers that support the defer attribute, the pop-up order is: "script", "defer", and "load". Note that the <script> element with the defer attribute is not executed after the second one, but is called before the onload event is triggered.
If your target browser only includes Internet Explorer and Firefox 3.5, the defer script is indeed useful. If you need to support multiple browsers across domains, there are more consistent ways to implement it.
HTML 5 defines a new extended attribute for the <script> tag: async. Its function is the same as defer, and it can load and execute scripts asynchronously, without blocking page loading because of loading scripts. However, one thing to note is that in the case of async, JavaScript scripts will be executed once they are downloaded, so it is very likely that they are not executed in the original order. If there are dependencies before and after JavaScript scripts, it is very likely that an error will occur using async.
Dynamic script elements
The Document Object Model (DOM) allows you to dynamically create almost all document content of HTML using JavaScript. The <script> element, like other elements of the page, can be created very easily with standard DOM functions:
Listing 6 Create <script> elements with standard DOM functions
var script = document.createElement ("script"); script.type = "text/javascript"; script.src = "script1.js"; document.getElementsByTagName("head")[0].appendChild(script);The new <script> element loads the script1.js source file. Download this file immediately after the element is added to the page. The key point of this technology is that no matter where the download is started, the download and running of the file will not block other page processing. You can even place these codes in the <head> section without affecting the page code for the rest (except for HTTP connections used to download files).
When a file is downloaded using a dynamic script node, the returned code is usually executed immediately (except Firefox and Opera, they will wait for all previous dynamic script nodes to complete execution). This mechanism works fine when the script is of the "self-running" type, but if the script only contains interfaces for invocation of other scripts on the page, it will cause problems. In this case, you need to keep track of whether the script download is completed and whether it is ready. You can use dynamic <script> nodes to issue events to get relevant information.
Firefox, Opera, Chorme and Safari 3+ will issue an onload event after the <script> node reception is completed. You can listen to this event to get notifications prepared by the script:
Listing 7 Loading JavaScript scripts by listening to onload events
var script = document.createElement ("script")script.type = "text/javascript";//Firefox, Opera, Chrome, Safari 3+script.onload = function(){ alert("Script loaded!");};script.src = "script1.js";document.getElementsByTagName("head")[0].appendChild(script);Internet Explorer supports another implementation that emits a readystatechange event. The <script> element has a readyState property whose value changes as the process of downloading external files. There are five values for readyState:
1. "uninitialized": default status
2. "loading": Downloading starts
3. "loaded": Download completed
4. "interactive": The download is completed but not available yet
5. "complete": All data is ready
Microsoft documentation says that during the life cycle of the <script> element, these values of readyState may not appear, but do not indicate which values will always be used. In practice, what we are most interested in is the "loaded" and "complete" states. Internet Explorer does not have the final state represented by these two readyState values. Sometimes the <script> element will get a "loader" but never "complete", but in some cases "complete" appears and "loaded" cannot be used. The safest way is to check these two states in the readystatechange event, and when one of the states appears, delete the readystatechange event handle (ensure that the event will not be processed twice):
Listing 8 Loading JavaScript scripts by checking readyState status
var script = document.createElement("script")script.type = "text/javascript";//Internet Explorerscript.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; alert("Script loaded."); }};script.src = "script1.js";document.getElementsByTagName("head")[0].appendChild(script);In most cases, you want to call a function to implement dynamic loading of JavaScript files. The following functions encapsulate the functions required by standard implementations and IE implementations:
Listing 9 Encapsulation by function
function loadScript(url, callback){ var script = document.createElement ("script") script.type = "text/javascript"; if (script.readyState){ //IE script.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; callback(); } }; } else { //Others script.onload = function(){ callback(); }; } script.src = url; document.getElementsByTagName("head")[0].appendChild(script);}This function receives two parameters: the URL of the JavaScript file, and a callback function that is triggered when JavaScript reception is completed. Attribute checking is used to determine which event to monitor. In the last step, set the src property and add the <script> element to the page. This loadScript() function is used as follows:
Listing 10 How to use loadScript() function
loadScript("script1.js", function(){ alert("File is loaded!");});You can dynamically load many JavaScript files on the page, but be careful that the browser does not guarantee the order in which files are loaded. Among all major browsers, only Firefox and Opera ensure that scripts are executed in the order you specify. Other browsers will download and run different code files in the order the server returns them. You can concatenate download operations together to ensure their order, as follows:
Listing 11 Load multiple JavaScript scripts through loadScript() function
loadScript("script1.js", function(){ loadScript("script2.js", function(){ loadScript("script3.js", function(){ alert("All files are loaded!"); }); }); }); });This code waits for script1.js to be available before starting to load script2.js, and then start to load script3.js after script2.js is available. Although this method is feasible, it is still a bit troublesome if there are many files to download and execute. If the order of multiple files is very important, a better way is to connect the files into one file in the correct order. A standalone file can download all the code at once (since this is done asynchronously, there is no loss in using a large file).
Dynamic script loading is the most commonly used pattern in non-blocking JavaScript downloads because it can be cross-browser and is easy to use.
Using XMLHttpRequest(XHR) object
This technique first creates an XHR object, then downloads a JavaScript file, and then injects JavaScript code into the page with a dynamic <script> element. Listing 12 is a simple example:
Listing 12 Loading JavaScript scripts through XHR objects
var xhr = new XMLHttpRequest();xhr.open("get", "script1.js", true);xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ var script = document.createElement ("script"); script.type = "text/javascript"; script.text = xhr.responseText; document.body.appendChild(script); } }};xhr.send(null);This code sends a GET request to the server to get the script1.js file. The onreadystatechange event handler checks whether readyState is 4, and then checks whether the HTTP status code is valid (2XX means a valid response, 304 means a cached response). If a valid response is received, a new <script> element is created and its text attribute is set to the responseText string received from the server. Doing so will actually create a <script> element with inline code. Once the new <script> element is added to the document, the code will be executed and ready to be used.
The main advantage of this approach is that you can download JavaScript code that is not executed immediately. Since the code returns outside the <script> tag (in other words, it is not subject to the <script> tag), it will not be executed automatically after downloading, which allows you to postpone the execution until everything is ready. Another advantage is that the same code does not throw exceptions in all modern browsers.
The main limitation of this method is that JavaScript files must be placed in the same domain as the page and cannot be downloaded from CDN (CDN refers to "Content Delivery Network", so large web pages usually do not use XHR script injection technology.
Add some functions you usually use
function loadJs(url, callback, charset) { var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); if ( !!charset) script.charset = "utf-8"; script.src = url; script.onload = script.onreadystatechange = function() { var f = script.readyState; if (f && f != "loaded" && f != "complete") return; script.onload = script.onreadystatechange = null; head.removeChild(script) if (callback) { callback() || callback }; }; head.appendChild(script);} // js synchronous loading function getScripts(i, linkArray, fn) { env || getEnv(); var script = document.createElement('script'); script.type = 'text/javascript'; script.src = linkArray[i]; var head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(script); if (env.ie && 'onreadystatechange' in script && !('draggable' in script)){ //IE browser loads script.onreadystatechange = function () { if (/loaded|complete/.test(script.readyState)) { script.onreadystatechange = null; if(i === linkArray.length-1) { if (fn) { fn(); } } else { getScripts(++i, linkArray, fn); } } } }; }else{ script.onload = function() { if(i === linkArray.length-1) { if (fn) { fn(); } } else { getScripts(++i, linkArray, fn); } } }; }}// js has a dependency loading getScripts(0, [ 'http://caibaojian.com/demo/base.js', 'http://caibaojian.com/demo/reset.js'], function() { alert('callback');});Summarize
There are several ways to reduce the performance impact of JavaScript:
Putting all <script> tags at the bottom of the page, that is, before closing the tag, this ensures that the page has been rendered before the script is executed.
Merge scripts as much as possible. The fewer <script> tags in the page, the faster it will load and the faster it will respond. This is true for both external scripts and embedded scripts.
Use unblocked JavaScript scripts to download:
Use the defer attribute of the <script> tag (only available for IE and Firefox 3.5 or higher);
Use dynamically created <script> elements to download and execute code;
Use XHR objects to download JavaScript code and inject it into the page.
The above strategies can greatly improve the actual performance of web sites and applications that require a lot of JavaScript.
The above is the full content of the JavaScript performance optimization summary loading and execution. I hope it will be helpful to everyone.