High-performance JavaScript

Chapter 1: Loading and execution

JavaScript performance in browsers => Most browsers use a single process to handle user interface (UI) refresh and JavaScript script execution, so you can only do one thing at a time, and the longer JavaScript execution takes, the longer the browser waits for a response

Each occurrence of the tag leaves the page waiting to be parsed and executed, no matter what form the JavaScript code exists in the file

The HTML4 specification states that

In theory: Putting together and loading scripts related to style and behavior helps ensure correct page rendering and interaction, but it still blocks page loading and leaves the user with a blank page during script parsing, although some browsers allow parallel loading of JavaScript files. However, JavaScript downloads still block other resources, such as images. Although the JavaScript download process does not affect each other, it simply cannot continue until all the JavaScript code has been downloaded and executed, so it is recommended that all

The first rule of JavaScript load optimization: Put the script at the bottom

Organize scripts

Multiple JavaScript scripts are merged into one to help the page load,

Loading four 25KB JavaScript scripts takes longer than loading a single 100KB JavaScript script

Yahoo provides a JavaScript script merge handler that puts multiple JavaScript script urls into one

Non-blocking script

This means that the JavaScript code is loaded after the page is loaded, or in technical terms, after the load event on the window object is triggered.

Delays in the script

HTML4 is not an ideal cross-browser solution and will be ignored in other browsers.

The JavaScript with defer will be parsed on the page

Dynamic script elements

Dynamically add the script DOM with createElement(‘script’). The script. Onload event is used to get the status of when the script is loaded

XMLHttpRequest Script injection

var xhr = new XMLHttpRequest();
xhr.open("get", file.js, true);
xhr.onreadystatechange = function () {
	if(xhr.reasdyState == 4) {
		if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304 ) {
			var script = document.createElement('script');
			script.type = "text/javascript";
			sctip.test = xhr.responseText;
			document.body.appendChild(script)
		}
	}
}
xhr.send(null)
Copy the code

Get the file.js file by sending a Get request and create a script tag once a valid response is received

Advantages: JavaScript code can be downloaded but not executed immediately, compatible with various browsers

Limitations: Cross-domain problems

Chapter two: Data access

There are four basic data storage locations in JavaScript:

literal

Literals represent themselves and are not stored in a specific location. JavaScript literals include strings, numbers, booleans, arrays, formations, functions, regular expressions, and special null and undefined values.

The local variable

The developer uses the keyword var to define the data storage unit

Array element

Stored inside a JavaScript array object, indexed by a number

Members of the object

Stored inside a JavaScript object, indexed by a string

Access speed

In general, the speed of access to literals and local variables is better for array items and object members. If you care about access speed, try to use local variables and literals and reduce the use of array items and object members.

There are several ways to locate and avoid problems and optimize code:

Administrative scope

First understand the scope:

Scope chains and identifier parsers

Each JavaScript Function is an instance of a Function object, and contains internal properties that are accessible only to the JavaScript engine. [[scope]] is one of them. It contains a collection of objects in the scope in which the Function is created, called the scope chain, and determines which data can be accessed

Tips: The execution environment of a function is unique each time it is executed, so running the same function multiple times creates multiple execution environments. After the function is executed, the environment is destroyed.

The chain of scopes created each time a function is run is pushed to the top of the chain as an active object.

Scope chain search process, from the first began to search at the top of the scope chain (i.e., active objects), if found will use this identifier corresponding variables, or continue to search next object of the scope chain Until we find, if you cannot find, then is undefined, it is this search process affects the performance.

Return the first variable if two variables with the same name exist in the scope chain;

Performance of identifier resolution

The deeper the identifier, the slower the read and write speed. Rule of thumb: If a cross-scoped value is referenced more than once in a function, store it in a local variable

function initUI() {
	var bd = document.body,
	links = document.getElementByTagName('a'),
	i = 0,
	len = links.length;
	wile(i < len) {
		update(links[i++]);
	}
	document.getElementById('go-on').onclick = function() {
		start();
	}
	bd.className = "active";
}
Copy the code

This function refers to document three times. Document is a global object, and each time it is used, it has to go to the bottom scope, affecting performance.

After the optimization:

function initUI() {
	var doc = document,
  bd = doc.body,
	links = doc.getElementByTagName('a'),
	i = 0,
	len = links.length;
	wile(i < len) {
		update(links[i++]);
	}
	doc.getElementById('go-on').onclick = function() {
		start();
	}
	bd.className = "active";
}
Copy the code

Change the scope chain

The with statement can change the scope chain

Modified code:

function initUI() {
	  with(document) { / / to avoid
      var bd = body,
          links = getElementByTagName('a'),
          i = 0,
          len = links.length;
      wile(i < len) {
            update(links[i++]);
          }
      getElementById('go-on').onclick = function() {
            start();
          }
       bd.className = "active"; }}Copy the code

With generates a new scope chain, meaning that the local variables of the function are stored in the second place, and the heart scope generated by with is placed first in the scope chain, making it more expensive to find

The catch statement in try-catch has the same problem

Dynamic scope

function execute(code) {
	eval(code);
	function sub() {
		return window;
	}
	var w = sub()
}
Copy the code

Execute (“var window = {}”); When w = {}, this code only finds problems at run time, so don’t use dynamic scopes unless necessary.

Closures, scopes, and memory

Closures: This allows functions to access data outside of local variables, but using closures can cause performance problems.

Try to understand the performance issues associated with closures

function assignEvent() {
	var id = "sdi333";
	document.getElementById("save").onclick = function (event) {
		saveDocument(id)
	}
}
Copy the code

Because the closure’s chain of scope contains references to objects that are the same as the execution environment, there are side effects,

Reduce overhead by storing commonly used cross-scoped variables in local variables and then accessing local variables directly.

Members of the object
The prototype

Objects have two types of members: power members, which are directly present in object instances, and prototype members, which are inherited from object prototypes

var book = {
	title: "High Performent JavaScript".publisher: "Press!!!"
}
alert(book.toString()); // "[object object]"
Copy the code

The book object executes without the toString method

Prototype chain
function Book(title,publisher){
  this.title = title;
  this.publisher = publisher
}
Book.prototype.sayTitle = function() {
  alert(this.title)
}
var book1 = new Book("Heigh Performation JavaScript"."Press!!!");
var book2 = new Book("JavaScript: Title"."Press!!!!")
alert(book1 instanceof Book);
alert(book1 instanceof Object);
book1.sayTitle(); // "Heigh Performation JavaScript"
alert(book1.toString()) // "[object Object]"

Copy the code

Members of the nested

For example: Windows. Location. Href

The more deeply nested the object members are, the more time it takes to find them. Window.location. href is always more time than location.href, and it takes more time to parse the prototype chain if necessary

Cache object members

summary

In JavaScript, where data is stored has a significant impact on overall code performance. Data can be stored in four ways: literals, variables, array items, and object members. They have their own performance characteristics:

  • Literals and local variables are the fastest to access, whereas array items and object members are slower to access
  • Because local variables exist at the start of the scope chain, accessing local variables is faster than accessing cross-scope variables. The deeper a variable is in the scope chain, the longer it takes to access. Since global variables are always at the end of the scope chain, they are also the slowest to access.
  • Avoid using the with statement because it alters the chain of scope in the execution environment, as does the catch clause in try-catch statements, so use it with caution.
  • Nested object members can significantly affect performance, so use them sparingly
  • The deeper a property or method is in the prototype chain, the slower it is to access it.
  • In general, you can improve JavaScript performance by storing commonly used object members, array items, cross-domain variables, and so on in local variables, which are faster to access.

Chapter 3 DOM programming

The following three types of issues are discussed in this chapter:

  • Access and modify DOM elements

  • Modifying DOM element styles results in repaint and reflow

  • Interaction with the user through DOM event handling

First —- what is DOM? Why is it slow?

DOM in the browser

The Document Object Model (DOM) is a language-independent programming interface (API) for manipulating XML and HTML documents.

Browsers typically implement DOM and JavaScript separately

Is inherently slow

Simple understanding: Two independent functions, as long as they are connected to each other through an interface, will consume.

DOM access and modification

Access to DOM elements comes at a cost, and modifying them is more expensive because it causes the browser to recalculate the geometry of the page.

The worst case scenario is to access and modify elements in a loop, especially over a collection of HTML elements

Code examples:

function innerHTMLLoop() {
	for(var count = 0; count < 15000; count++) {
    document.getElementById('here').innerHtml += "a"; }}Copy the code

Each iteration of the loop accesses the element twice: once to read the innerHTML property value and once to override it

Take a more efficient approach:

function innerHTMLLoop() {
  let str = ' '
	for(var count = 0; count < 15000; count++) {
     str += "a";
  }
  document.getElementById('here').innerHtml += str;
}
Copy the code

General rule of thumb: Minimize DOM access and keep operations on the ECMAScript side as much as possible.

InnerHTML compares the DOM method

The innerHTML and document.createElement() methods for modifying page areas are almost the same in performance, but innerHTML is faster in the latest edition of the browser

But in everyday use, the two performance is similar

Node cloning

Another way to update page content using DOM methods is to clone existing elements instead of creating new ones. Element.colonenode () (element denotes an existing node) instead of document.createElement()

HTML collection

An HTML collection is an array-like object that contains references to DOM nodes. The return value of the following methods is a collection:

  • document.getElementsByName()

  • document.getElementsByClassName()

  • document.getElementsByTagName()

The following attributes also return the HTML collection:

All the IMG elements in the Document.images page

Document.links All a elements

Document.forms [0].elements all fields of the first form on the page

The above methods and attributes return an HTML collection, which is an array-like list. They are not really an array, but they have a length attribute like the one in an array, and they can access the elements of the list numerically.

In fact, the HTML collection is always connected to the document, and the query process is repeated every time you need the latest information, even if it’s just to get the number of elements in the collection (that is, to access the length attribute of the collection). This is a source of inefficiency.

Expensive set
var alldivs = document.getElemetnsByTagName('div');
for (var i = 0; i < alldivs.length; i++) {
  document.body.appendChlid(document.createElement('div'))}Copy the code

This code is in an infinite loop, citation alldivs grows in length with each iteration.

Use local variables when accessing collections
/ / slowly
function collectionGlobal() {
  var coll = docuemnt.getElementsByTagName('div'),
      len = coll.length,
      name = "";
  for (var count = 0; count < len; count++) {
    name = document.getElementsByTagName('div')[count].nodeName;
    name = document.getElementsByTagName('div')[count].nodeType;
    name = document.getElementsByTagName('div')[count].tagName;
  }
  return name;
}
/ / faster
function collectionLocal() {
  var coll = docuemnt.getElementsByTagName('div'),
      len = coll.length,
      name = "";
  for (var count = 0; count < len; count++) {
    name = coll[count].nodeName;
    name = coll[count].nodeType;
    name = coll[count].tagName;
  }
  return name;
}
/ / the fastest
function collectionLocal() {
  var coll = docuemnt.getElementsByTagName('div'),
      len = coll.length,
      name = "",
      el = null;
  for (var count = 0; count < len; count++) {
    el = coll[count]
    name = el.nodeName;
    name = el.nodeType;
    name = el.tagName;
  }
  return name;
}
Copy the code
Element nodes

ChildNodes, firstChild, and nextSibling do not distinguish between element nodes and other types of nodes (e.g., comment and text nodes (Spaces between two nodes)). In some cases you need to filter out non-element nodes. These types check for a filter that is actually an unnecessary DOM operation.

Using children instead of childNodes is faster because there are fewer collections.

The selectors API
var elements = docuemnt.querySelectorAll('#menu a');
var elements = docuemnt.gerElementById('menu').getElementsByTagName('a')
Copy the code

As above: querySelectorAll() is faster.

So: with a lot of combined queries to handle, using querySelectorAll() is more efficient.

Redraw and rearrange

After downloading all the components of the page ————HTML tags, JavaScript, CSS, images ————, the browser parses and generates two internal data structures:

The DOM tree

Presentation page structure

Render tree

How are DOM nodes displayed

Every node in the DOM tree that needs to be displayed has at least one corresponding node in the render tree (hidden DO elements have no corresponding node in the render tree). The nodes in the rendering tree are called “frames” or “boxes,” which fit the CSS model, interpreting the page elements as a box with the padding, margins, borders, and positions of gay friends. Once the DOM and the fume tree are built, the browser begins to display (draw “paint”) page elements.

When a DOM change affects an element’s collection properties (width and height) – such as changing the border width or adding text to a paragraph, resulting in an increase in the number of lines – the browser needs to recalculate the element’s geometry, as well as the geometry and position of other elements. The browser invalidates the affected portion of the render tree and reconstructs the render tree. This process is called reflow. When the rearrangement is complete, the browser redraws the affected parts onto the screen, a process called repaint.

When rearrangement occurs

When the layout and geometry of the page changes, it needs to be rearranged. A rearrangement occurs in the following situations.

  • Add or remove visible DOM elements.

  • Element position changes.

  • Element size changes (including: margin, inner margin, border thickness, width, height, etc.).

  • Content changes, such as text changes or an image being replaced with a different size image.

  • The page renderer is initialized.

  • Browser window size changed

Corresponding portions of the render tree, large or small, also need to be recalculated depending on the extent and extent of the changes, which trigger a rearrangement of the entire page: for example, when a scrollbar appears.

Queuing and refreshing of render tree changes

Operations that retrieve layout information cause a queue refresh, as shown in the following example:

  • offsetTop, offsetLeft, offsetRight, officeWidth
  • scrollTop, scrollLeft, scrollRight, scrollWidth
  • clientTop, clientLeft, clientRight, clientWidth
  • GetComputedStyle () (currentStyle in IE)
var computed,
    tmp = "",
    bodystyle = document.body.style;
if(document.body.currentStyle){ // IE, OPERA
  computed = document.body.currentStyle;
}else { //W3C
  computed = document.defaultView.getComputedStyle(document.body,' ');
}
// An inefficient way to modify the same attribute
// Then get the style information
// bad
bodyStyle.color = 'red';
tmp = computed.backgroundColor;
bodyStyle.color = 'white';
tmp = computed.backgroundImage;
bodyStyle.color = 'green';
tmp = computed.backgroundAttachment;
// good
bodyStyle.color = 'red';
bodyStyle.color = 'white';
bodyStyle.color = 'green';
tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;
Copy the code

Minimize redraw and rearrange

Change the style
// bad
var el = document.getElementById('mydiv');
el.style.borderLeft = '1px; '
el.style.borderRight = '2px; '
el.style.padding = '5px; '

// good
var el = document.getElementById('mydiv');
el.style.cssText = 'border-left:1px; border-right:2px; padding:5px; ';

// Change the class name of the CSS once
var 
el = document.getElementById('mydiv');
el.className = 'active';
Copy the code
Batch modify DOM

When you need to perform a series of operations on a DOM element, you can reduce the number of redraws and rearrangements by following these steps:

  1. Take the document out of the document flow
  2. Apply multiple changes to it
  3. Bring the element back into the document

This process triggers two rearrangements – step 1 and step 3. If you ignore these two steps, any changes made in step 2 will trigger a rearrangement.

Cache layout information

When you query layout information, such as offsets, scroll values, or computed style values, the browser will refresh the queue to apply all changes in order to return the latest values.

/ / and inefficient
myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if(myElement.offsetLeft >= 500) {
  stopAnimation()
}

// good
current++
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if(current >= 500) {
  stopAnimation()
}
Copy the code
Take the element out of the animation flow

Use these steps to avoid most rearrangements of pages:

  1. Use absolute location to locate animated elements on the page out of the document flow.
  2. Let the elements move. When he expands. Some pages are temporarily overwritten. But this is only a small area of the page redrawn process, does not result in rearranging and redrawing large parts of the page.
  3. When the animation ends, the location resumes, thus moving down the rest of the document only once.

summary

Accessing and manipulating the DOM is an important part of modern Web applications. But every time you cross the bridge that connects the islands of ECMAScript and DOM, you will be charged a “bridge toll”. To minimize the performance penalty of DOM programming, keep a few things in mind:

  • Minimize DOM access and handle it on the JavaScript side as much as possible.
  • If you need to access a DOM node more than once, use local variables to store references to it.
  • Be careful with the HTML collection because it connects to the underlying document in real time. Cache the length of the collection in a variable and use it in iterations. If you need to manipulate the collection frequently, it is recommended that you copy it into an array.
  • If possible, use faster apis such as querySelectorAll() and firstElementChild.
  • Pay attention to redrawing and rearranging; When changing styles in batches, manipulate the DOM tree “offline,” use caching, and reduce the number of times you access layout information.
  • Use absolute positioning in animation, using drag and drop agents
  • Use event delegates to reduce the number of time handlers.