This article mainly from the Angle of the performance optimization to explore the JavaScript is loaded and executed in the process of optimizing idea and practice method, is not only in detail, this paper in relation to the original rational place, not going to say a few words, still hope readers to be patient, careful understanding, please believe that, your patience will be giving you get matching returns.

origin

With the increasing emphasis on user experience, the impact of front-end performance on user experience has attracted much attention. However, due to the relatively complex causes of performance problems, it is difficult for us to comprehensively solve them from a certain aspect or several aspects, which is also the reason for this article. I want to take this article as a starting point. A series of articles will explore and sort out all aspects of Javascript performance in order to fill and consolidate my knowledge structure.

The directory structure

The general writing ideas of this paper include but are not limited to:

  • Have to say JavaScript blocking features

  • Place the script properly to optimize the loading experience, and place the JS script before the tag closes.

  • Reduce the number of HTTP requests and simplify script code.

  • Loading JavaScript scripts without blocking:

    • Use the defer attribute of the

    • Use HTML5 async properties.

    • Dynamically create

    • Load JavaScript using XHR objects.

I have to say about the blocking nature of JavaScript

Front-end developers should know that JavaScript is single-threaded, that is, when JavaScript is running a block of code, other things on the page (UI updates, other script loads, etc.) are suspended at the same time and cannot be processed at the same time. Therefore, when executing a JS script, This code affects other operations. This is a JavaScript feature that we can’t change.

This feature of JavaScript is called blocking, and because of this blocking feature, front-end performance optimization, especially for JavaScript, becomes relatively complicated.

Why is it blocked?

If JavaScript’s blocking nature causes so many problems, you might ask, why can’t the JavaScript language just do what Java and other languages do with multithreading?

To fully understand the single-threaded design of JavaScript, it’s easy to summarize: JavaScript was originally designed to improve the user experience of web pages on the browser side, handling simple tasks like form validation on a page. So, at the time, JavaScript was doing very little, and there wasn’t a lot of code, which was a strong correlation between JavaScript and interface manipulation.

Since JavaScript is strongly related to interface manipulation, we can think of it this way: For example, if there are two JS scripts in a page that change the content of a DOM element, if JavaScript adopts multi-threaded processing mode, then the content displayed in the final page element will be the result of which JS script operation, because the two JS scripts are loaded from different threads. We don’t want to be able to predict who will finish first, and this kind of interface data updating is common in JavaScript. Therefore, it is easy to understand why JavaScript is designed with single threads: JavaScript is designed with single threads to avoid the repetition of unpredictable changes to page content during execution.

For more information about JavaScript, you can see the birth of JavaScript written by Ruan Yifeng

Optimizations for loading: Place scripts properly

Due to the blocking nature of JavaScript, every <script> occurrence, either inline or chained, leaves the page waiting for the script to load, parse and execute, and the <script> tag can be placed in the <head> or <body> section of the page. Therefore, If the CSS and JS references on our page are in different order or locations, the loading experience will be different even for the same code. Here’s an example:


      
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Js reference location performance optimization</title>
    <script type="text/javascript" src="index-1.js"></script>
    <script type="text/javascript" src="index-2.js"></script>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
Copy the code

The above code is a simple HTML interface, which loaded two JS script files and a CSS style file. Due to the js blocking problem, when loading the index-1.js, the following content will be suspended and wait until the index-1.js is loaded and executed. Then the second script file index-2.js will be executed. At this time, the page will be suspended again and wait for the completion of loading and execution of the script. In this way, when the user opens the interface, the content of the interface will be delayed obviously, and we will see a blank page flash. Therefore, we should try to make the content and style appear first, and put the JS file at the end of to optimize the user experience.


      
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Js reference location performance optimization</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div id="app"></div>
    <script type="text/javascript" src="index-1.js"></script>
    <script type="text/javascript" src="index-2.js"></script>
  </body>
</html>
Copy the code

Optimize from number of requests: Reduce number of requests

One thing we need to know: Page loading process, the most time consuming not to load and execute js itself, by contrast, every time go to the backend access to resources, the client to establish links with the background is the most time consuming, which is well-known Http three-way handshake, of course, the Http request is not we discuss the theme of this time, want to further understand their own search, a lot of related articles on the network.

Therefore, reducing HTTP requests is one of our major optimizations. In fact, its optimization effect is very significant in many cases where JS script files are loaded on the page. To reduce the number of HTTP requests, you have to move to compact files.

File simplification and compression

In order to reduce access requests, it is necessary to use JS minifucation and compression **. It should be noted that compact files are not complicated, but improper use can also lead to errors or invalid code problems. Therefore, in practical use, It is best to parse the JS syntax before compression to help us avoid unnecessary problems (such as files containing Chinese unicode transcoding problems).

There are three common parsing compression tools: YUI Compressor, Closure Complier and UglifyJs

YUI Compressor: The advent of YUI Compressor, considered the most popular parser-based compression tool, removes comments and extra whitespace from code and replaces local variables with a single or two character to save more bytes. By default, however, substitution that can cause errors, such as with or eval(), is turned off;

Closure Complier: Closure Complier is also a parser based compression tool that tries to make your code as small as possible. It will remove comments and extra whitespace and make variable substitutions, and it will analyze your code to optimize it accordingly, such as removing variables that you have defined but are not using, and turning variables that have only been used once into inline functions.

UglifyJs: Considered the first Node.js-based compression tool, UglifyJs removes comments and extra whitespaces, replaces variable names, merges var expressions, and makes some other optimizations

Each tool has its own advantages, such as YUI compression is accurate, Closure compression is smaller, UglifyJs does not rely on Java but is based on JavaScript and has fewer errors than Closure. I don’t know which one is better. Developers should make their own choices based on the situation of their project.

Optimized for loading: non-blocking script loading

In terms of JavaScript performance optimization, reducing the size of script files and limiting the number of HTTP requests is only the first step to make the interface respond quickly. Nowadays, Web applications are rich in functions and JS scripts are increasing. It is not always feasible to simplify the size of source code and reduce the number of requests, even if it is an HTTP request, but the file is too large. Interfaces can also be locked for long periods of time, which is obviously not good, hence the non-blocking loading technique.

Simply put, this means that the js code is loaded after the page is loaded, i.e. the script is downloaded after the load event of the window object is triggered. To implement this approach, the following methods are commonly used:

Delayed script loading (defer)

HTML4 later defined an extended attribute for the <script> tag: defer. What the defer attribute does is indicate that the script to be loaded does not modify the DOM, so the code is safe to defer, and that all major browsers now support defer.

<script type="text/javascript" src="index-1.js" defer></script>
Copy the code

The <script> tag with the defer attribute is not executed until the DOM is finished loading, inline or chained.

Lazy script loading (async)

The HTML5 specification also introduces the async attribute, which is used for loading scripts asynchronously. Its general function is the same as defer. It adopts parallel downloading and there will be no blocking in the downloading process, but the difference lies in their execution timing. However, defer will wait until the page has loaded.

Optimized for loading: add script elements dynamically

The advantage of adding code dynamically is that no matter when the script is downloaded, it will not be downloaded or executed by the rest of the page, and we can even add it directly to the head tag without affecting the rest of the page.

So, as a developer, you’ve no doubt seen code blocks like this:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'file.js';
document.getElementsByTagName('head') [0].appendChild(script);
Copy the code

This approach is called dynamic script creation, which we now refer to as dynamic script creation. Once the file is downloaded in this way, the code is automatically executed. In modern browsers, however, this script waits until all dynamic nodes have loaded. In this case, in order to ensure that the interface or method of other code contained in the current code can be successfully called, the code must be prepared before the other code is loaded. The specific operation ideas are as follows:

Modern browsers receive a load event after the content of the script tag has been downloaded. After the load event, we can load and run the code we want to execute. In IE, it receives loaded and complete events. But practice tells us that the two events seem to have no order, and sometimes only one of them can be obtained. We can separately encapsulate a special function to reflect the practicality of this function, so a unified writing method is as follows:

 function LoadScript(url, callback) {
        var script = document.createElement('script');
        script.type = 'text/javascript';

        // In Internet Explorer
        if (script.readyState) {
          script.onreadystatechange = function () {
            if (script.readyState == 'loaded' || script.readyState == 'complete') {
              // Make sure to execute twice
              script.onreadystatechange = null;
              // Todo executes the code to execute
              callback()
            }
          }
        } else {
          script.onload = function () {
            callback();
          }
        }

        script.src = 'file.js';
        document.getElementsByTagName('head') [0].appendChild(script);
      }

Copy the code

The LoadScript function accepts two parameters, namely the script path to load and the callback function to execute after loading. The LoadScript function itself has the feature detection function. Based on the detection result (IE and other browsers), the LoadScript function determines which event to listen for during the script processing.

In fact, the LoadScript() function here is the prototype of what we call lazyload.js.

With this method, we can implement a simple multi-file loading block in a fixed order:

LoadScript('file-1.js'.function(){
  LoadScript('file-2.js'.function(){
    LoadScript('file-3.js'.function(){
        console.log('loaded all')})})})Copy the code

When the above code is executed, file-1.js will be loaded first, then file-2.js will be loaded, and so on. This is certainly debatable (multiple callback nesting is hell), but the idea of dynamic script addition, as well as the issues to be aware of and avoid during loading, is clarified in the LoadScript function.

Of course, if there are too many files and the order of loading is required, the best solution is to suggest that they be loaded together in the correct order, which is generally better.

Optimized for loading: XMLHttpRequest script injection

Fetching a script via an XMLHttpRequest object and injecting it into a page is another way to achieve non-blocking loading, which I find easy to understand. This is the same idea as adding a script dynamically. Here’s the code:

var xhr = new XMLHttpRequest();
xhr.open('get'.'file-1.js'.true);
xhr.onreadystatechange = function() {
  if(xhr.readyState === 4) {
    if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {// If data is fetched from background or cache, add it to script and load it.
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.text = xhr.responseText;
      // Add the created script to the document page
      document.body.appendChild(script); }}}Copy the code

The data obtained in this way has two advantages: First, we can control whether the script executes immediately, because we know that the newly created script tag will execute as soon as it is added to the document interface, so we can implement the requirements according to our actual business logic before adding the document interface (before appendChild()), When you want it to execute, appendChild(). Second, it is compatible with all the major browsers. It does not require us to write our own feature checking code, like adding scripts dynamically.

The downside, however, is that because you are using XHR objects, there is a “domain” limit to obtaining this resource. Resources must reside in the same domain.

The final summary

This article mainly explores front-end optimization solutions from the loading and execution of JavaScript, and lists the advantages and disadvantages of each solution in detail. Of course, front-end performance optimization is relatively complex, and there is still a long way to go if you want to thoroughly understand its various original causes!

The main writing ideas of this paper:

  • Have to say JavaScript blocking features

  • Place the script properly to optimize the loading experience, and place the JS script before the tag closes.

  • Reduce HTTP requests, compress and simplify script code.

  • Loading JavaScript scripts without blocking:

    • Use the defer attribute of the

    • Use HTML5 async properties.

    • Dynamically create

    • Load JavaScript using XHR objects.

Finally, due to personal level reasons, if there is incomplete or omission of mistakes, please readers criticism and correction, all the way you, very grateful! .

Thanks to this era, we can stand on the shoulders of giants and peep out the magnificence of the program world. I would like to travel through the mountains and rivers of the program world with a pure heart! May every colleague walking in the world of program live the way they want in their hearts, come on!