Browser environment overview

JavaScript is the built-in scripting language of the browser. In other words, the browser has a built-in JavaScript engine and provides various interfaces so that JavaScript scripts can control various functions of the browser. Once the JavaScript script is embedded in the webpage, the browser loads the webpage and executes the script, so as to achieve the purpose of operating the browser and realize various dynamic effects of the webpage.

This chapter begins to introduce the various JavaScript interfaces provided by the browser. First, introduce the method of embedding JavaScript code into web page.

How to embed the code on a webpage

There are four main ways to embed JavaScript code in a web page.

-The <script> element is directly embedded in the code. -<script> tag to load external script -Event attributes -URL protocol

script element embed code

JavaScript code can be written directly inside the <script> element.

<script>
  var x = 1 + 5;
  console.log(x);
</script>

The <script> tag has a type attribute to specify the script type. For JavaScript scripts, the type attribute can be set to two values.

-text/javascript: This is the default value and the value that has always been set in history. If you omit the type attribute, it will default to this value. For older browsers, this value is better. -application/javascript: For newer browsers, it is recommended to set this value.

<script type="application/javascript">
  console.log("Hello World");
</script>

Because the <script> tag is JavaScript code by default. Therefore, when embedding JavaScript scripts, the type attribute can be omitted.

If the value of the type attribute is not recognized by the browser, then it will not execute the code in it. Using this, you can embed any text content in the <script> tag, as long as you add a type attribute that the browser does not recognize.

<script id="mydata" type="x-custom-data">
  console.log('Hello World');
</script>

The above code will not be executed by the browser, nor will it display its content, because it does not recognize its type attribute. However, this <script> node still exists in the DOM, and its content can be read out using the text attribute of the <script> node.

document.getElementById("mydata").text;
// console.log('Hello World');

script element to load external script

The <script> tag can also specify to load an external script file.

<script src="https://www.example.com/script.js"></script>

If the script file uses non-English characters, the encoding of the characters should also be noted.

<script charset="utf-8" src="https://www.example.com/script.js"></script>

The loaded script must be pure JavaScript code, without HTML code and <script> tags.

Load external scripts and directly add code blocks, these two methods cannot be mixed. The console.log statement in the following code is directly ignored.

<script charset="utf-8" src="example.js">
  console.log('Hello World!');
</script>

In order to prevent attackers from tampering with external scripts, the script tag allows to set an integrity attribute, and write the Hash signature of the external script to verify the consistency of the script.

<script
  src="/assets/application.js"
  integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="
></script>

In the above code, the script tag has an integrity attribute, which specifies the SHA256 signature of the external script /assets/application.js. Once someone changes this script, the SHA256 signature does not match, the browser will refuse to load.

Event attributes

The event attributes of web page elements (such as onclick and onmouseover) can be written in JavaScript code. These codes are called when the specified event occurs.

<button id="myBtn" onclick="console.log(this.id)">click</button>

The above event attribute code has only one statement. If there are multiple statements, use semicolons to separate them.

URL protocol

URL supports the javascript: protocol, that is, code is written in the URL position, and JavaScript code will be executed when the URL is used.

<a href="javascript:console.log('Hello')">click</a>

The browser's address bar can also execute the javascript: protocol. Put javascript:console.log('Hello') into the address bar and press the Enter key to execute this code.

If the JavaScript code returns a string, the browser will create a new document, display the content of this string, and the content of the original document will disappear.

<a href="javascript: new Date().toLocaleTimeString();">click</a>

In the above code, after the user clicks the link, a new document will be opened with the current time in it.

If the returned string is not a string, then the browser will not create a new document, nor will it jump.

<a href="javascript: console.log(new Date().toLocaleTimeString())">click</a>

In the above code, after the user clicks on the link, the web page will not jump, only the current time will be displayed in the console.

The common use of the javascript: protocol is the bookmark script Bookmarklet. Since the bookmark of the browser saves a URL, the URL of javascript: can also be saved in it. When the user selects this bookmark, the script will be executed on the current page. To prevent bookmarks from replacing the current document, you can add void before the script, or add void 0 at the end of the script.

<a href="javascript: void new Date().toLocaleTimeString();">click</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">click</a>

In the above two ways, after clicking the link, the execution code will not jump to the web page.

script element

working principle

The browser loads JavaScript scripts, mainly through the <script> element. The normal web page loading process is like this.

  1. The browser starts parsing while downloading the HTML page. In other words, do not wait until the download is complete, start parsing.
  2. During the parsing process, if the browser finds the <script> element, it pauses the parsing and transfers the control of web page rendering to the JavaScript engine.
  3. If the <script> element references an external script, download the script and execute it, otherwise, execute the code directly.
  4. After the JavaScript engine is executed, the control is returned to the rendering engine, and the HTML web page parsing is resumed.

When an external script is loaded, the browser will pause page rendering and wait for the script to be downloaded and executed before continuing to render. The reason is that JavaScript code can modify the DOM, so control must be given to it, otherwise it will lead to complicated thread races.

If the external script takes a long time to load (it has been unable to complete the download), the browser will wait for the script download to complete, causing the web page to lose response for a long time, and the browser will appear "fake death" state, which is called the "blocking effect" .

In order to avoid this situation, it is better to put the <script> tags at the bottom of the page instead of the head. In this way, even if the script becomes unresponsive, the rendering of the main body of the web page has been completed, and the user can at least see the content instead of facing a blank page. If some script code is very important and must be placed at the head of the page, it is best to write the code directly into the page instead of connecting to an external script file, which can shorten the loading time.

Script files are loaded at the end of the web page, there is another advantage. Because the DOM node is called before the DOM structure is generated, JavaScript will report an error. If the script is loaded at the end of the page, this problem does not exist, because the DOM must have been generated at this time.

<head>
  <script>
    console.log(document.body.innerHTML);
  </script>
</head>
<body></body>

An error will be reported when the above code is executed, because the document.body element has not been generated yet.

One solution is to set the callback function of the DOMContentLoaded event.

<head>
  <script>
    document.addEventListener("DOMContentLoaded", function (event) {
      console.log(document.body.innerHTML);
    });
  </script>
</head>

In the above code, the relevant code is executed only after the specified DOMContentLoaded event occurs. The DOMContentLoaded event will only be triggered after the DOM structure is generated.

Another solution is to use the onload attribute of the <script> tag. When the external script file specified by the <script> tag is downloaded and parsed, a load event will be triggered, and the code to be executed can be placed in the callback function of this event.

<script
  src="jquery.min.js"
  onload="console.log(document.body.innerHTML)"
></script>

However, if you put the script at the bottom of the page, you can write it in the normal way, neither of the above two ways.

<body>
  <!-- Other code-->
  <script>
    console.log(document.body.innerHTML);
  </script>
</body>

If there are multiple script tags, such as the following.

<script src="a.js"></script>
<script src="b.js"></script>

The browser will download both a.js and b.js in parallel at the same time, but it will ensure that a.js is executed first, and then b.js is executed, even if the latter is downloaded first. . In other words, the execution order of scripts is determined by the order in which they appear on the page. This is to ensure that the dependencies between scripts are not destroyed. Of course, loading these two scripts will have a "blocking effect", you must wait until they are both loaded, the browser will continue to render the page.

Parsing and executing CSS will also cause blocking. The Firefox browser will wait until all the style sheets in front of the script are downloaded and parsed before executing the script; Webkit will suspend the execution of the script once it finds that the script references a style, and then resume execution when the style sheet is downloaded and parsed.

In addition, for resources from the same domain, such as script files, stylesheet files, image files, etc., browsers generally have restrictions, and a maximum of 6-20 resources can be downloaded at the same time, that is, there are restrictions on the maximum number of TCP connections that can be opened at the same time. This is for Prevent too much pressure on the server. If it is a resource from a different domain name, there is no such restriction. Therefore, static files are usually placed under different domain names to speed up the download speed.

defer attribute

In order to solve the problem of script file download blocking web page rendering, one method is to add the defer attribute to the <script> element. Its function is to delay the execution of the script, and then execute the script after the DOM is loaded and generated.

<script src="a.js" defer></script>
<script src="b.js" defer></script>

In the above code, a.js and b.js will be executed only after the DOM has been loaded.

The operation flow of the defer attribute is as follows.

  1. The browser starts to parse the HTML page.
  2. During the parsing process, a <script> element with a defer attribute was found.
  3. The browser continues to parse the HTML web page, and at the same time downloads the external script loaded by the <script> element in parallel.
  4. The browser finishes parsing the HTML webpage, and then goes back and executes the script that has been downloaded.

With the defer attribute, the browser will not block page rendering when downloading script files. The downloaded script files are executed before the DOMContentLoaded event is triggered (that is, the </html> tag has just been read), and it can be guaranteed that the execution order is the order in which they appear on the page.

For script tags that are built-in instead of loading external scripts, and for dynamically generated script tags, the defer attribute does not work. In addition, external scripts loaded with defer should not use the document.write method.

async attribute

Another way to solve the "blocking effect" is to add the async attribute to the <script> element.

<script src="a.js" async></script>
<script src="b.js" async></script>

The function of the async attribute is to use another process to download the script without blocking rendering while downloading.

  1. The browser starts to parse the HTML page.
  2. During the parsing process, a script tag with the async attribute was found.
  3. The browser continues to parse the HTML web page, and at the same time downloads the external script in the <script> tag in parallel.
  4. After the script download is complete, the browser pauses to parse the HTML webpage and starts to execute the downloaded script.
  5. After the script is executed, the browser resumes parsing the HTML page.

The async attribute can ensure that the browser continues to render while the script is downloaded. It should be noted that once this attribute is adopted, the execution order of the script cannot be guaranteed. Which script is downloaded first, execute that script first. In addition, the code in the script file that uses the async attribute should not use the document.write method.

Which one of the defer attribute and the async attribute should be used?

Generally speaking, if there is no dependency between scripts, the async attribute is used, and if there is a dependency between scripts, the defer attribute is used. If the async and defer attributes are used at the same time, the latter will not work, and the browser behavior is determined by the async attribute.

Dynamic loading of scripts

The <script> element can also be dynamically generated, and then inserted into the page after generation, so as to realize the dynamic loading of the script.

["a.js", "b.js"].forEach(function (src) {
  var script = document.createElement("script");
  script.src = src;
  document.head.appendChild(script);
});

The advantage of this method is that the dynamically generated script tag will not block the page rendering, and will not cause the browser to die. But the problem is that this method cannot guarantee the execution order of the scripts. Which script file is downloaded first will be executed first.

If you want to avoid this problem, you can set the async attribute to false.

["a.js", "b.js"].forEach(function (src) {
  var script = document.createElement("script");
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

The above code will not block page rendering, and it can ensure that b.js is executed after a.js. However, it should be noted that the script file loaded after this code will be executed after the completion of b.js execution.

If you want to specify a callback function for dynamically loaded scripts, you can use the following writing method.

function loadScript(src, done) {
  var js = document.createElement("script");
  js.src = src;
  js.onload = function () {
    done();
  };
  js.onerror = function () {
    done(new Error("Failed to load script " + src));
  };
  document.head.appendChild(js);
}

Load the used protocol

If you do not specify the protocol, the browser defaults to download using HTTP protocol.

<script src="example.js"></script>

The above example.js defaults to download using HTTP protocol. If you want to download using HTTPS protocol, you must specify it.

<script src="https://example.js"></script>

But sometimes we would like to decide the loading protocol according to the protocol of the page itself. At this time, we can use the following writing method.

<script src="//example.js"></script>

The composition of the browser

The core of the browser is two parts: rendering engine and JavaScript interpreter (also known as JavaScript engine).

Rendering engine

The main function of the rendering engine is to render the webpage code into a flat document that the user can visually perceive.

Different browsers have different rendering engines.

-Firefox: Gecko engine -Safari: WebKit engine -Chrome: Blink engine -IE: Trident engine -Edge: EdgeHTML engine

The rendering engine processes web pages and is usually divided into four stages.

  1. Parse the code: HTML code is parsed into DOM, and CSS code is parsed into CSSOM (CSS Object Model).
  2. Object synthesis: Combine DOM and CSSOM into a render tree.
  3. Layout: Calculate the layout of the render tree.
  4. Draw: draw the render tree to the screen.

The above four steps are not strictly performed in sequence, and often the second and third steps have already begun before the first step has been completed. Therefore, you will see this situation: the HTML code of the web page has not been downloaded yet, but the browser has already displayed the content.

Reflow and redraw

The rendering tree is transformed into a web page layout, which is called "flow"; the process of displaying the layout on the page is called "paint". They all have a blocking effect and consume a lot of time and computing resources.

After the page is generated, both script operations and style sheet operations will trigger "reflow" and "repaint". User interaction also triggers reflow and redrawing, such as setting the mouse hover (a:hover) effect, scrolling the page, entering text in the input box, changing the window size, and so on.

Re-streaming and re-drawing do not necessarily occur together, re-streaming will inevitably lead to re-drawing, and re-drawing does not necessarily require re-streaming. For example, changing the color of an element will only cause redrawing, but not reflow; changing the layout of an element will cause redrawing and reflow.

In most cases, the browser will make intelligent judgments and limit the reflow and redrawing to only the relevant subtrees, minimizing the cost, and will not regenerate the web page globally.

As a developer, you should try to reduce the frequency and cost of redrawing. For example, try not to change the high-level DOM elements, but replace them with changes in the low-level DOM elements; for example, redrawing the table layout and the flex layout will cost more.

var foo = document.getElementById("foobar");

foo.style.color = "blue";
foo.style.marginTop = "30px";

The above code will only cause one redraw, because the browser will accumulate DOM changes and then execute it all at once.

Here are some optimization tips.

-Read DOM or write DOM, try to write together, don't mix. Don't read a DOM node, then write it immediately, and then read another DOM node. -Cache DOM information. -Don't change the styles one by one, but use CSS classes to change the styles all at once. -Use documentFragment to manipulate the DOM -The animation uses absolute positioning or fixed positioning, which can reduce the impact on other elements. -Show hidden elements only when necessary. -Use window.requestAnimationFrame(), because it can postpone the execution of the code until the next redraw, instead of requiring the page to be redrawn immediately. -Use virtual DOM (virtual DOM) library.

The following is an example of the contrast effect of window.requestAnimationFrame().

// Re-streaming is expensive
function doubleHeight(element) {
  var currentHeight = element.clientHeight;
  element.style.height = currentHeight * 2 + "px";
}

all_my_elements.forEach(doubleHeight);

// Low cost of redrawing
function doubleHeight(element) {
  var currentHeight = element.clientHeight;

  window.requestAnimationFrame(function () {
    element.style.height = currentHeight * 2 + "px";
  });
}

all_my_elements.forEach(doubleHeight);

The first piece of code above, every time the DOM is read, a new value is written, which will cause constant rearrangement and reflow. The second piece of code accumulates all write operations, so that the cost of DOM code changes is minimized.

JavaScript engine

The main function of the JavaScript engine is to read the JavaScript code in the web page, process it and run it.

JavaScript is an interpreted language, that is, it does not need to be compiled and is run by the interpreter in real time. The advantage of this is that it is more convenient to run and modify, and the interpretation can be re-interpreted after refreshing the page; the disadvantage is that the interpreter must be called every time it is run, the system overhead is large, and the running speed is slower than the compiled language.

In order to improve the running speed, current browsers compile JavaScript to a certain degree to generate intermediate code similar to bytecode to improve the running speed.

In the early days, the processing of JavaScript inside the browser was as follows:

  1. Read the code, perform lexical analysis, and decompose the code into tokens.
  2. Perform grammatical analysis (parsing) on ​​lexical elements and organize the code into a "syntax tree".
  3. Use the "translator" to convert the code to bytecode.
  4. Use "bytecode interpreter" to convert bytecode to machine code.

Interpreting line by line to convert bytecode to machine code is very inefficient. In order to increase the running speed, modern browsers adopt "Just In Time compiler" (JIT), which means that the bytecode is compiled only at runtime, and which line is compiled when it is used, and the compiled result is cached ( inline cache). Usually, a program is often used, but only a small part of the code. With the cached compilation result, the running speed of the entire program will be significantly improved.

Bytecode cannot be run directly, but runs on a virtual machine (Virtual Machine), which is generally called a JavaScript engine. Not all JavaScript virtual machines have bytecodes at runtime. Some JavaScript virtual machines are based on source code, that is, whenever possible, the source code is directly compiled into machine code and run by a JIT (just in time) compiler, omitting bytecode. step. This is different from other languages ​​that use virtual machines (such as Java). The purpose of this is to optimize the code and improve performance as much as possible. Here are some of the most common JavaScript virtual machines:

-Chakra (Microsoft Internet Explorer) -Nitro/JavaScript Core (Safari) -Carakan (Opera) -SpiderMonkey (Firefox) -V8 (Chrome, Chromium)