Preface
I believe everyone has encountered many issues regarding JavaScript script loading. Mainly at several points -
1> Problems with file loading, file dependency and execution order caused by synchronous scripts and asynchronous scripts
2> Performance optimization problems caused by synchronous scripts and asynchronous scripts
A deep understanding of all aspects of script loading is not only conducive to solving practical problems, but also conducive to grasping and executing performance optimization.
First look at any script tag code -
The code copy is as follows:
<script src="js/myApp.js"></script>
If placed on <head>, it will block all page rendering work, causing the user to remain in a "white screen of death" state until the script is loaded and executed. The script at the end of <body> will only let the user see the static page without vitality. Where the client rendering is supposed to be scattered with ineffective controls and empty boxes. Take a test case -
The code copy is as follows:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Async loading script</title>
<script src="js/test.js"></script>
</head>
<body>
<div>I am content</div>
<img src="img/test.jpg">
</body>
</html>
Among them, the content in test.js -
The code copy is as follows:
alert('I am the script code in the head. After executing the js here, the body content rendering begins!');
We will see that alert is a pause point, and at this time, the page is blank. However, be aware that the entire page has been loaded at this time. If the body contains tags for certain src attributes (such as the img tag above), the browser has started loading the relevant content at this time. In short, it should be noted that the working timing of the js engine and the rendering engine are mutually exclusive (some books call it UI thread).
Therefore, we need that scripts that are responsible for making the page look better and use better should be loaded immediately, and scripts that can be loaded later will be loaded later.
1. Script execution delay
Now it is becoming more and more popular to place scripts at the end of the page <body> tag. In this way, on the one hand, the user can see the page faster, and on the other hand, the script can directly operate the dom elements that have been loaded. This "moving" is a huge improvement for most scripts. The page model is as follows -
The code copy is as follows:
<!DOCTYPE html>
<html>
<head lang="en">
<!--metadata and scriptsheets go here-->
<script src="headScript.js"></script>
</head>
<body>
<!--content goes here-->
<script src="bodyScript.js"></script>
</body>
</html>
This does greatly speed up the rendering time of the page, but be aware that this may give users the opportunity to interact with the page before bodyScript is loaded. The reason why the browser cannot load the scripts before loading the full document is a big bottleneck for large documents transmitted over slow connections.
Ideally, the loading of the script should be done simultaneously with the loading of the document and does not affect the rendering of the DOM. This way, once the document is ready, the script can be run because the corresponding script has been loaded in the order of the <script> tag.
We can accomplish this requirement by using defer, that is,
The code copy is as follows:
<script src="deferredScript.js"></script>
Adding the defer attribute is equivalent to telling the browser: Please start loading this script immediately, but please wait until the document is ready and all scripts with the defer attribute have finished running before running it.
In this way, putting delay scripts in the head tag will bring all the benefits of placing scripts on the body tag, and it can greatly improve the loading speed of large documents. The page mode at this time is -
The code copy is as follows:
<!DOCTYPE html>
<html>
<head lang="en">
<!--metadata and scriptsheets go here-->
<script src="headScript.js"></script>
<script src="deferredScript.js" defer></script>
</head>
<body>
<!--content goes here-->
</body>
</html>
However, not all browsers support defer (for some modern browsers, if defer is declared, their internal scripts will not perform document.write and DOM rendering operations. Both IE4+ support defer attributes). This means that if you want to ensure that your delay script can run after the document is loaded, you must encapsulate the code of all delay scripts in a structure such as jQuery's $(document).ready. This is worth it, because almost 97% of visitors can enjoy the benefits of parallel loading, while another 3% of visitors can still use full-featured JavaScript.
2. Complete parallelization of scripts
Let the scripts be loaded and executed one step faster. I don’t want to wait until the defer scripts run one after another (defer reminds us of an ordered queueing scenario where the document is quietly waiting for the document to load), and I don’t want to wait until the document is ready before running these scripts. I want to load and run these scripts as soon as possible. Here we think of the async attribute of HTML5, but be aware that it is a chaotic anarchy.
For example, we load two completely irrelevant third-party scripts, and the page runs well without them, and don't care who runs first and who runs later. Therefore, using the async attribute on these third-party scripts is equivalent to improving their running speed without spending a penny.
The async attribute is newly added to HTML5. The function is similar to defer, that is, it allows DOM rendering while downloading scripts. However, it will be executed as soon as possible after downloading (i.e., the JS engine is idle and executed immediately), and there is no guarantee that the script will be executed in order. They will be completed before the onload event.
Firefox 3.6, Opera 10.5, IE 9, and the latest Chrome and Safari all support the async attribute. Async and defer can be used at the same time, so that all IEs after IE 4 support asynchronous loading, but be careful that async will overwrite defer.
Then the page model at this time is as follows -
The code copy is as follows:
<!DOCTYPE html>
<html>
<head lang="en">
<!--metadata and scriptsheets go here-->
<script src="headScript.js"></script>
<script src="deferredScript.js" defer></script>
</head>
<body>
<!--content goes here-->
<script src="asyncScript1.js" async defer></script>
<script src="asyncScript2.js" async defer></script>
</body>
</html>
Pay attention to the execution order here - each script file is loaded, then headScript.js is executed, and then defferedScript.js is loaded in the background while DOM rendering. Then, defferedScript.js and the two asynchronous scripts will be run at the end of DOM rendering. Note that for browsers that support the async attribute, these two scripts will be run out of order.
3. Programmable script loading
Although the functions of the above two script properties are very attractive, they are not widely used due to compatibility issues. Therefore, we use scripts to load other scripts more. For example, we only want to load a script for users who meet certain conditions, which is the often mentioned "lazy loading".
At the browser API level, there are two reasonable ways to crawl and run server scripts -
1> Generate ajax request and use the eval function to process the response
2> Insert the <script> tag into the DOM
The latter method is better because the browser will worry about generating HTTP requests for us. Furthermore, eval also has some practical problems: leaking scope, debugging is messy, and it may also reduce performance. Therefore, if you want to load a script named feature.js, we should use a code like the following:
The code copy is as follows:
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = 'feature.js';
head.appendChild(script);
Of course, we need to deal with callback listening, and the HTML5 specification defines an onload property that can bind callbacks.
The code copy is as follows:
script.onload = function() {
console.log('script loaded ...');
}
However, IE8 and older versions do not support onload, they support onreadystatechange. Moreover, there are still many strange things to deal with errors. Here, you can refer to some popular school-based loading libraries, such as labjs, yepnope, requirejs, etc.
As follows, I encapsulate a simple loadjs file myself -
The code copy is as follows:
var loadJS = function(url,callback){
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = url;
script.type = "text/javascript";
head.appendChild( script);
// script tag, there is onreadystatechange event under IE, and there is onload event under w3c standard
// IE9+ also supports onload of W3C standard
var ua = navigator.userAgent,
ua_version;
// IE6/7/8
if (/MSIE ([^;]+)/.test(ua)) {
ua_version = parseFloat(RegExp["$1"], 10);
if (ua_version <= 8) {
script.onreadystatechange = function(){
if (this.readyState == "loaded" ){
callback();
}
}
} else {
script.onload = function(){
callback();
};
}
} else {
script.onload = function(){
callback();
};
}
};
I won’t talk about the asynchronous loading of scripts in document.write. Now few people do this because the browser differences are really overwhelming.
Note that using Image object to preload js files asynchronously, the js code inside will not be executed.
Finally, let’s talk about the asynchronous loading script in requirejs.
requirejs does not guarantee the target scripts to be run sequentially, but only ensures that their running order can meet their respective dependency requirements. Therefore, we ensure that all scripts are loaded in parallel as soon as possible and execute them in an orderly manner according to the dependency topology.
4. Summary
OK, when it comes to this, the statement of the asynchronous loading script is over. Let me talk about the optimization order here again -
1> In the traditional way, we use script tags to directly embed them into html documents. Here are two situations -
a> Embed into the head tag - Be careful that doing so will not affect the parallel loading of other static resource files in the document content. It affects the rendering of the document content, that is, the DOM rendering at this time will be blocked and the white screen will be presented.
b> Embed at the bottom of the body tag - In order to avoid the white screen phenomenon, we give priority to rendering the DOM and then execute the script, but the problem comes again. Let’s talk about the first question first - if the content of the DOM document is relatively large, the interaction event binding will be delayed, and the experience will be a little worse. Of course, we need to make important scripts execute first based on needs. Let’s talk about the second problem - because the script files are as far as the bottom of the body, the loading of these scripts is delayed compared to the scripts in the head. Therefore, as for the bottom of the body, it is not the end point of optimization.
c> Add defer attribute - we hope the script will be loaded in parallel as soon as possible, and we will still put this batch of scripts into the head. The loading of the script should be done simultaneously with the loading of the document and does not affect the rendering of the DOM. This way, the script can be run once the document is ready. So there is a defer attribute. But pay attention to its compatibility. For browsers that do not support the defer attribute, we need to encapsulate the code in a $(document).ready such as jQuery. It should be noted that all scripts with defer attributes are executed in sequence according to their appearance order, so it is also strictly synchronized.
2> The previous point is about synchronous execution scripts (note that the loading process of these scripts is parallel, but the difference between who triggers the request first and who then triggers the request). The next optimization point is "parallel execution scripts". Of course, we know that at a point in time, only one js file is executed. The "parallel" here means that whoever loads first, as long as the js engine is idle at this time, it will be executed immediately. The optimization here is divided into two types -
a> Adding the async property - it can indeed complete the optimization point we mentioned above, but it has high limitations, that is, it is only for non-dependency script loading. The most appropriate example is to introduce multiple third-party scripts. Also, the combination with the deffer attribute is really a big deal. Of course, it also has compatibility issues. The above three problems have led to their infrequent application. When using async, you must pay close attention to dependency issues.
b> Script Loading Scripts - Obviously, we use this to achieve the purpose of "parallel execution of scripts". At the same time, we also facilitate control of script dependencies, so we use intelligent load management for asynchronous loading in requirejs.
OK, that's all.
Here, I'm just talking about the content related to asynchronous loading scripts. There is another part of the content, which is asynchronous loading of style files or other static resources. to be continued......