In this article, I wanted to document some useful optimizations after reading High Performance Javascript, and share some of my own experiences with you.

Loading and execution of Javascript

As we all know, when the browser parses the DOM tree, when the script tag is parsed, it blocks all other tasks until the JS file is downloaded and parsed. As a result, the browser will block here, and if the script tag is placed in the head, the user will only see a blank page before the JS file is loaded and executed, which is a terrible user experience. The common methods are as follows:

  • Placing all the script tags at the bottom of the body ensures that the JS file is loaded and executed last, rendering the page to the user first. However, you need to know if the first rendering of the page depends on your partial JS file, and if so, you need to put that partial JS file on the head.
  • Use defer, as follows. When you use defer, the browser will download the corresponding JS files when it parses the tag, but it will not execute them immediately. Instead, it will execute them after the DOM has been parsed (before the DomContentLoader). Therefore, it does not block the browser.
<script src="test.js" type="text/javascript" defer></script>
Copy the code
  • Dynamic loading of JS files. In this way, you can load the required code after the page is loaded, and also realize lazy loading/on-demand loading of JS files in this way. For example, nowadays, it is common to combine vue-router/react-router to achieve on-demand loading. The code is loaded only when a specific route is accessed. Specific methods are as follows:

1. Dynamically insert script tags to load scripts, such as the following code

  function loadScript(url, callback) {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    / / deal with IE
    if (script.readyState) {
      script.onreadystatechange = function () {
        if (script.readyState === 'loaded' || script.readyState === 'complete') {
          script.onreadystatechange = null; callback(); }}}else {
      // Handle other browser cases
      script.onload = function () {
        callback();
      }
    }
    script.src = url;
    document.body.append(script);
  }

  // Load js dynamically
  loadScript('file.js'.function () {
    console.log('Load complete');
  })
Copy the code

2. Load JS files in XHR mode, but in this way, you may face cross-domain problems. Examples are as follows:

  const xhr = new XMLHttpRequest();
  xhr.open('get'.'file.js');
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.text = xhr.responseText;
        document.body.append(script); }}}Copy the code

3. Merge multiple JS files into one file and compress them. The reason: By far the most browsers have support for parallel downloads js file, but the number of concurrent download has some restrictions (browser based, in part, the browser can only download 4), and each js files need to build an additional HTTP connection, loading of four 25 KB file compared to load a 100 KB file consumption of time. Therefore, it is best to merge multiple JS files into one and compress the code.

Javascript scopes

When a function is executed, an execution context is generated, which defines the context in which the function is executed. When the function completes execution, the execution context is destroyed. Therefore, calling the same function multiple times results in the creation of multiple execution contexts. Each execution context has its own chain of scopes. I’m sure you already know the scope, the first scope of a function is the variable inside the function. During the execution of a function, every variable encountered will be searched in the scope chain of the function to find the first matching variable, first looking for variables inside the function, and then looking layer by layer along the scope chain. Therefore, if we were to access the outermost variable (global variable), there would be a higher performance penalty than if we were to access the internal variable directly. Therefore, we can store frequently used global variable references in a local variable.

const a = 5;
function outter () {
  const a = 2;
  function inner () {
    const b = 2;
    console.log(b); / / 2
    console.log(a); / / 2
  }
  inner();
}
Copy the code

Object reading

In javascript, there are four main types: literals, local variables, array elements, and objects. Literals and local variables are the fastest to access, while array elements and object members are relatively slow to access. And when accessing object members, just like the scope chain, is to look up on the prototype chain. Therefore, if the member being searched is too deep in the prototype chain, the access is slower. Therefore, we should minimize the number of lookups and nesting depth of object members. Such as the following code

  // Perform two object member lookups
  function hasEitherClass(element, className1, className2) {
    return element.className === className1 || element.className === className2;
  }
  // optimize, if the variable does not change, then local variables can be used to hold the contents of the search
  function hasEitherClass(element, className1, className2) {
    const currentClassName = element.className;
    return currentClassName === className1 || currentClassName === className2;
  }
Copy the code

DOM manipulation optimization

  • Minimize DOM operations, use javascript whenever possible, and store DOM nodes using local variables whenever possible. For example:
  // Before optimization, each loop gets the node with id T and sets its innerHTML
  function innerHTMLLoop () {
    for (let count = 0; count < 15000; count++) {
      document.getElementById('t').innerHTML += 'a'; }}// After optimization,
  function innerHTMLLoop () {
    const tNode = document.getElemenById('t');
    const insertHtml = ' ';
    for (let count = 0; count < 15000; count++) {
      insertHtml += 'a';
    }
    tNode.innerHtml += insertHtml;
  }
Copy the code
  • Reduce rearrangement and redrawing as much as possible. Rearrangement and regrouping may be very expensive. Therefore, in order to reduce the occurrence of rearrangement and regrouping, we can make the following optimization

1. When we want to make changes to the Dom style, we should try to merge all changes and process them at once to reduce the number of rearrangements and recollections.

  / / before optimization
  const el = document.getElementById('test');
  el.style.borderLeft = '1px';
  el.style.borderRight = '2px';
  el.style.padding = '5px';

  // After optimization, change the style once so that three rearrangements can be reduced to one
  const el = document.getElementById('test');
  el.style.cssText += '; border-left: 1px ; border-right: 2px; padding: 5px; '
Copy the code

2. When we want to batch modify the DOM nodes, we can hide the DOM nodes, perform a series of modification operations, and then make them visible, so that we can only do two rearrangements at most. Specific methods are as follows:

  // Before optimization
  const ele = document.getElementById('test');
  // A series of DOM modification operations

  // Optimization plan 1: Set the node to be modified to not be displayed, then modify it, and display the node after modification, so that only two rearrangements are required
  const ele = document.getElementById('test');
  ele.style.display = 'none';
  // A series of DOM modification operations
  ele.style.display = 'block';

  // first create a documentFragment, then modify the fragment, then insert the documentFragment into the document, only when the last documentFragment is inserted into the document will cause a rearrangement, so only one rearrangement will be triggered.
  const fragment = document.createDocumentFragment();
  const ele = document.getElementById('test');
  // A series of DOM modification operations
  ele.appendChild(fragment);
Copy the code

3. Use event delegate: Event delegate is to move the event of the target node to the parent node for processing. Due to the characteristics of browser bubble, when the target node triggers the event, the parent node will also trigger the event. Therefore, the parent node is responsible for listening and handling the event.

So what are the advantages? Suppose you have a list in which each item needs to be bound to the same event, and the list may be frequently inserted and deleted. As usual, you can only bind one event handler to each list item, and you need to register a new event handler for each list item you insert. As a result, if the list item is large, there will be too many event handlers, causing significant performance problems. With event delegate, we only need to listen for the event at the parent node of the list item and let it handle the event uniformly. In this way, no additional processing is required for newly added list items. And the use of event delegates is actually quite simple:

function handleClick(target) {
  // Click the handle event for the list item
}
function delegate (e) {
  // Check whether the target object is a list item
  if (e.target.nodeName === 'LI') { handleClick(e.target); }}const parent = document.getElementById('parent');
parent.addEventListener('click', delegate);
Copy the code

This article address in -> my blog address, welcome to give a start or follow