JS engine in-depth analysis-reprint

Source: Internet
Author: User
Tags message queue script tag blank page mozilla developer network

July 16, 2016 16:46:39Hits: 2110
    • The composition of the browser
    • Ways to embed JavaScript code in a Web page
    • Add code blocks directly
    • Loading external scripts
    • In-line code
    • How the <script> tag works
    • Defer property
    • Async Property
    • Re-flow and redraw
    • Dynamic embedding of scripts
    • Load the protocols used
    • JavaScript virtual machine
    • Single Thread model
    • Meaning
    • Message Queuing
    • Event Loop
    • Reference links
The composition of the browser

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

(1) Rendering engine

The main purpose of the rendering engine is to "render" a Web page from code to 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, typically divided into four stages.

    1. Parsing code: HTML code resolves to dom,css code parsing to Cssom (CSS Object Model)
    2. Object compositing: Compositing Dom and Cssom into a render tree
    3. Layout: Calculate the layout of the render tree (layout)
    4. Drawing: Drawing a render tree to the screen

The above four steps are not strictly executed in sequence, often the first step is not completed, the second and third steps have already begun. So, you'll see this: the HTML code for the page hasn't been downloaded yet, but the browser has already shown the content.

(2) JavaScript engine

The main purpose of the JavaScript engine is to read the JavaScript code in the Web page and run it after it is processed.

This section mainly describes how the JavaScript engine works.

Ways to embed JavaScript code in a Web page

JavaScript code can only be run if it is embedded in a Web page. There are several ways to embed JavaScript code in a Web page.

Add code blocks directly

With <script> tags, you can embed JavaScript code directly into a Web page.

<script>console.log(‘Hello World‘);</script>

<script>The tag has a type property that specifies the type of script. However, if you embed a JavaScript script, the type attribute can be omitted.

For JavaScript scripts, type properties can be set to two values.

    • text/javascript: This is the default value, and it is the historically set value. If you omit type the attribute, this value is the default. For older browsers, it is better to set this value.
    • application/javascript: For newer browsers, it is recommended to set this value.
Loading external scripts

scriptTags can also specify script files that are loaded externally.

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

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

<script charset="utf-8"src="example.js"></script>

Load external scripts and add code blocks directly, neither of these methods can be mixed. The statements in the following code console.log are ignored directly.

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

To prevent an attacker from tampering with an external script, the script tag allows you to set a integrity property that writes 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 label has a integrity property that specifies the /assets/application.js SHA265 signature of the external script. Once someone changes the script, causing the SHA265 signature to be mismatched, the browser will refuse to load.

In addition to JavaScript scripts, the external CSS stylesheet can also set this property.

In-line code

In addition to the above two methods, the HTML language allows you to a write JavaScript directly into the event properties and element attributes of some elements href .

<divonclick="alert(‘Hello‘)"></div><ahref="javascript:alert(‘Hello‘)"></a>

This writing, which mixes HTML code with JavaScript code, is very detrimental to code management and is not recommended.

<script>How the labels work

The normal page loading process is like this.

    1. Browser while downloading HTML page, start parsing
    2. During the parsing process, the Discovery <script> label
    3. Pause parsing, control of page rendering to the JavaScript engine
    4. If the <script> tag refers to an external script, download the script, or execute it directly
    5. After execution, control is returned to the rendering engine, parsing the HTML page down

That is, when the external script is loaded, the browser pauses the page rendering, waits for the script to download and executes, and then continues rendering. The reason is that JavaScript can modify the DOM (for example document.write , using methods), so it has to give control to it, otherwise it can lead to complex thread race problems.

If the external script takes a long time to load (for example, it has been unable to complete the download), it will cause the webpage to lose its response for a long time, and the browser will appear "suspended animation", which is called "blocking effect".

To avoid this, it's a good idea to <script> put the labels at the bottom of the page, not the head. This way, even if a script loses its response, the rendering of the body of the Web page is finished, and the user can at least see the content instead of facing a blank page.

If some script code is important and must be placed on the head of the page, it is best to embed the code directly into the page instead of connecting the external script file, which can shorten the load time.

There is also a benefit to placing the script files at the end of the page. Calling Dom,javascript before the DOM structure is generated will be an error, and if the script is loaded at the end of the page, there is no problem because the DOM must have been generated.

The above code executes with an error because the document.body element is not yet generated.

One workaround is to set DOMContentLoaded the callback function for the event.

Another workaround is to use <script> the properties of the label onload . When the <script> external script file specified by the tag is downloaded and parsed, a load event is triggered, and the code to be executed can be placed in the callback function of the 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 two ways.

<body><!-- 其他代码 --><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 concurrently in parallel, but execution will be guaranteed to execute before execution, a.js b.js a.js b.js Even if the latter is downloaded first. In other words, the order in which the scripts are executed is determined by the order in which they appear in the page, in order to ensure that dependencies between scripts are not compromised.

Of course, loading both scripts creates a "blocking effect" that must wait until they are loaded and the browser will continue to render the page.

After the gecko and WebKit engines are blocked, a second thread parses the document, downloads the external resources, but does not modify the DOM, or the Web page is blocked.

Parsing and executing CSS can also cause blocking. Firefox waits until all the stylesheets in front of the script are downloaded and parsed, and then executes the script; The WebKit is that once the script references the style, it pauses execution of the script, waits until the stylesheet is downloaded and parsed, and resumes execution.

In addition, for resources from the same domain name, such as script files, style sheet files, picture files, and so on, the browser generally download up to six simultaneously (IE11 allow simultaneous download of 13). If it is a resource from a different domain name, there is no such limit. Therefore, static files are usually placed under different domain names to speed up the download.

Defer property

To solve the problem of scripting file download blocking page rendering, one way is to add the defer attribute.

<script src="1.js"defer></script><script src="2.js"defer></script>

deferThe function of the property is to tell the browser to wait until the DOM load is complete before executing the specified script.

    1. Browser starts parsing HTML page
    2. During parsing, defer a script tag with attributes is found
    3. The browser continues to parse the HTML page and concurrently downloads the external script in the script tag
    4. The browser finishes parsing the HTML page and then executes the downloaded script

With defer properties, the browser does not block page rendering when it downloads script files. The downloaded script file DOMContentLoaded executes before the event is triggered (that is, just after the tag has been read ), and the order of execution is guaranteed to be the order in which they appear on the page.

The properties do not work for script tags built into the external script, and for dynamically generated script tags defer .

Async Property

Another way to solve the "blocking effect" is to add async attributes.

<script src="1.js"async></script><script src="2.js"async></script>

asyncThe function of a property is to download the script using another process that does not block rendering when it is downloaded.

    1. Browser starts parsing HTML page
    2. During parsing, async labels with attributes are found script
    3. The browser continues to parse the HTML page and simultaneously downloads script the external script in the tag
    4. The script download completes, the browser pauses parsing the HTML page, starts to execute the downloaded script
    5. The browser resumes parsing the HTML Web page after the script is executed

asyncProperty ensures that the browser continues rendering while the script is being downloaded. It is important to note that once this attribute is used, the execution order of the scripts cannot be guaranteed. Which script will download the end first, execute the script first. In addition, async a method should not be used in a script file that uses attributes document.write .

deferWhich of the properties and async attributes should be used?

In general, if there are no dependencies between scripts, use async attributes and use attributes if there is a dependency between the scripts defer . If both the and attributes are used, the async defer latter does not work, and the browser behavior is determined by the async property.

Re-flow and redraw

The render tree is converted to a Web page layout, called a layout flow, and the layout is displayed to the page by this process, called paint. They all have blocking effects and can take a lot of time and computing resources.

After the page is generated, both the script actions and the style sheet actions trigger the heavy flow (reflow) and redraw (repaint). User interaction will also trigger, 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.

Heavy flow and repainting do not necessarily occur together, and heavy flow inevitably results in repainting, and redrawing does not necessarily require a heavy flow. Changing the color of the element, for example, will only result in redrawing, without causing a heavy flow, and altering the layout of the element will result in redrawing and re-flow.

In most cases, the browser intelligently judges that the heavy flow and redraw are limited to the relevant subtree, minimizing the cost of the page, and not the global rebuild.

As a developer, you should try to reduce the number and cost of repainting. For example, try not to change the DOM elements of the upper layers, instead of changing the underlying DOM elements, for example, redrawing the table layout and flex layout, the overhead will be larger.

varfoo= document.getElementById(‘foobar‘);foo.style.color=‘blue‘;foo.style.marginTop=‘30px‘;

The above code only causes one redraw, because the browser accumulates DOM changes and executes at once.

Here are some optimization techniques.

    • Read the DOM or write to the DOM, try to write it together, don't mix it up
    • Caching DOM Information
    • Instead of changing the style one by one, use CSS class to change the style at once
    • Manipulating the DOM using document fragment
    • Use absolute positioning or fixed positioning when animating, which can reduce the impact on other elements
    • Display elements only when necessary
    • Use window.requestAnimationFrame() , as it can defer code to the next heavy stream, rather than immediately requiring page re-flow
    • Using the Virtual DOM Library

Here is an window.requestAnimationFrame() example of a contrasting effect.

// 重绘代价高functiondoubleHeight(element) {  varcurrentHeight=element.clientHeight;  element.style.height= (currentHeight*2) +‘px‘;}all_my_elements.forEach(doubleHeight);// 重绘代价低functiondoubleHeight(element) {  varcurrentHeight=element.clientHeight;  window.requestAnimationFrame(function () {    element.style.height= (currentHeight*2) +‘px‘;  });}all_my_elements.forEach(doubleHeight);
Dynamic embedding of scripts

In addition to using static script tags, you can also embed script tags dynamically.

[‘1.js‘, ‘2.js‘].forEach(function(src) {  varscript= document.createElement(‘script‘);  script.src=src;  document.head.appendChild(script);});

The advantage of this approach is that dynamically generated script labels do not block page rendering and do not cause the browser to feign death. The problem, however, is that this approach does not guarantee the order in which scripts are executed, which script files are first downloaded and executed first.

If you want to avoid this problem, you can set the Async property to false .

[‘1.js‘, ‘2.js‘].forEach(function(src) {  varscript= document.createElement(‘script‘);  script.src=src;  script.async=false;  document.head.appendChild(script);});

The above code still does not block page rendering and is guaranteed to be 2.js 1.js executed later. It is important to note, however, that the script file that is loaded after this code will wait for the 2.js execution to complete before executing.

We can encapsulate the above notation as a function.

(function() {  varscripts= document.getElementsByTagName(‘script‘)[0];  functionload(url) {    varscript= document.createElement(‘script‘);    script.async=true;    script.src=url;    scripts.parentNode.insertBefore(script, scripts);  }  load(‘//apis.google.com/js/plusone.js‘);  load(‘//platform.twitter.com/widgets.js‘);  load(‘//s.thirdpartywidget.com/widget.js‘);}());

In the above code, the async properties true are set to because the loaded scripts are not interdependent. Moreover, this will not cause clogging.

In addition, there is one place to be aware of dynamic embedding. Dynamic embedding must wait for the CSS file to be loaded before the external script file is downloaded. There is no problem with static loading, the script external script file specified by the tag is concurrently downloaded with the CSS file.

Load the protocols used

If you do not specify a protocol, the browser is downloaded by default with the HTTP protocol.

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

The above example.js default is the HTTP protocol download, if you want to use the HTTPS protocol download, it is necessary to specify (assuming that the server support).

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

But sometimes we would like to, according to the page itself protocol to determine the load protocol, then you can use the following wording.

<script src="//example.js"></script>
JavaScript virtual machine

JavaScript is an interpreted language, meaning that it does not need to be compiled and can be run by the interpreter in real time. The advantage is that the operation and modification are more convenient, refresh the page can be re-explained, the disadvantage is that each run to invoke the interpreter, the system overhead, running slower than the compiled language. To improve the speed of operation, the current browser compiles JavaScript to some extent, generating intermediate code like bytecode (bytecode) to improve the speed of operation.

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

    1. Read the code, parse it (Lexical analysis), and break the code down into lexical elements (tokens).
    2. The word meta is parsed (parsing) and the code is organized into a "syntax tree" (syntax).
    3. Using the Translator (translator), convert the code to bytecode (bytecode).
    4. Use the bytecode interpreter (bytecode interpreter) to convert the bytecode to machine code.

A line-by-row explanation for converting bytecode to machine code is inefficient. To improve the speed of operation, modern browsers instead use "Instant compile" (Just in Time compiler, abbreviated JIT), that is, the bytecode is compiled only at runtime, which line is used to compile which line, and the compiled result is cached (inline cache). Usually, a program is often used, but only a small portion of the code, with the cached compilation results, the entire program will run a significant increase in speed.

Different browsers have different compilation strategies. Some browsers only compile the most frequently used parts, such as the loop part, and some browsers simply omit the bytecode translation steps and compile them directly into machine code, such as the V8 engine of the Chrome browser.

Bytecode does not run directly, but rather runs on top of a virtual machine, which is generally referred to as the JavaScript engine. Because JavaScript does not necessarily have bytecode at runtime, JavaScript VMs are not completely based on bytecode, but are partly based on source code, which compiles the source code directly into the machine code with the JIT (just in time) compiler whenever possible, omitting the bytecode step. This differs from other languages that use virtual machines, such as Java. This is done in order to optimize the code as much as possible and to improve performance. Here are some of the most common JavaScript virtual machines available:

    • Chakra (Microsoft Internet Explorer)
    • Nitro/javascript Core (Safari)
    • Carakan (Opera)
    • SpiderMonkey (Firefox)
    • V8 (Chrome, Chromium)
Single Threading model meaning

First, make a clear idea that JavaScript runs on only one thread and does not mean that the JavaScript engine has only one thread. In fact, the JavaScript engine has multiple threads, where a single script can only run on one thread, and the other threads are in the background mates. JavaScript scripts run in a thread. This means that only one task can be run at a time, and all other tasks must be queued later.

JavaScript is single-threaded, not multi-threaded, and is related to history. JavaScript is single-threaded since it was born because it doesn't want to make the browser too complicated, because multithreading requires sharing resources and potentially modifying the results of each other's operations, which is too complex for a web scripting language. For example, assuming that JavaScript has two threads at the same time, one thread adds content to one of the DOM nodes, and the other thread deletes the node, which thread should the browser take precedence over? So, to avoid complexity, JavaScript is a single thread from birth, which has become a core feature of the language and will not change in the future.

To take advantage of the computational power of multicore CPUs, HTML5 proposes a web worker standard that allows JavaScript scripts to create multiple threads, but the child threads are completely controlled by the main thread and must not manipulate the DOM. So, this new standard does not change the nature of JavaScript single threading.

The single-threaded model poses some problems, mainly because the new task is added to the tail of the queue, and only the previous task is completed before it is executed. If a task is particularly time-consuming, the subsequent tasks will stop there waiting, causing the browser to lose its response, also known as "suspended animation." To avoid "suspended animation", when an operation does not end after a certain amount of time, the browser jumps out of the prompt and asks if the user wants to force the script to stop running.

If the queue is because of large computational capacity, the CPU is not busy, but also forget, but many times the CPU is idle, because the IO device (input) is very slow (such as the Ajax operation from the network to read data), have to wait for the results, and then down to execute. The designer of the JavaScript language realizes that at this point the CPU can completely ignore the IO device, suspend the waiting task, and run the task in the queue first. Wait until the IO device returns the result, and then go back and put the suspended task on hold. This mechanism is the event Loop used within JavaScript.

Message Queuing

In addition to a running thread, the JavaScript runtime provides a message queue, which is a variety of messages that need to be processed by the current program. When a new message enters the queue, it is automatically queued at the end of the queue.

When a running thread finds that the message queue is not empty, it takes out the first message and executes its corresponding callback function. Wait until the execution is complete, and then take out the second message, looping until the message queue becomes empty.

Each message is associated with a callback function, which means that the program executes the corresponding function as soon as it receives the message. On the other hand, messages that enter Message Queuing must have corresponding callback functions. Otherwise the message will be lost and will not enter the message queue. For example, a mouse click produces a message that reports that click the event has occurred. If there is no callback function, the message is lost. If there is a callback function, this message enters the message queue. When the program receives the message, it executes the callback function of the Click event.

Another scenario is setTimeout to add a message to the message queue at a specified time. If there is no other message at this time in the message queue, the message will be processed immediately, otherwise the message will have to wait until other messages have been processed before it is processed. Therefore, setTimeout the specified execution time is only one of the earliest possible time, and there is no guarantee that it will happen at that time.

Once the current execution stack is empty, Message Queuing takes out the first message and passes in the program. The program starts executing the corresponding callback function, waits until the execution is finished, and then processes the next message.

Event Loop

The so-called event loop, which refers to an internal loop, is used to process messages in the message queue round and round, that is, to execute the corresponding callback function. The definition of Wikipedia is: "Event loop is a program structure for waiting and sending messages and events (a programming construct that waits for and dispatches events or messages in A program) ". You can interpret the event loop as a dynamically updated message queue itself.

Here are some common JavaScript tasks.

    • Executing JavaScript code
    • Respond to user input (including mouse clicks, keyboard input, and so on)
    • Handling Asynchronous Network requests

All tasks can be divided into two types, one synchronization task (synchronous) and one asynchronous task (asynchronous). A synchronization task is a task that is queued on the JavaScript execution process to perform the latter task only if the previous task is completed, and the asynchronous task refers to a task that does not enter the JavaScript execution process and goes into the task queue, only the " The task queue notifies the main process that an asynchronous task can be executed, and the task (in the form of a callback function) is entered into the JavaScript process execution.

As an example of an AJAX operation, it can be handled as a synchronous task or as an asynchronous task, as determined by the developer. In the case of a synchronous task, the main thread waits for the Ajax operation to return the result and then executes it, and if it is an asynchronous task, the task goes directly to the task queue, and the JavaScript process skips the Ajax operation and executes directly until the AJAX operation has the result. The JavaScript process then executes the corresponding callback function.

That is, while JavaScript has only one process to execute, there are other processes in parallel (for example, processes that process timers, processes that process user input, processes that handle network traffic, and so on). These processes enable communication with the JavaScript process by adding tasks to the task queue.

To understand the event Loop, you should start with the program's operating mode. A program that runs later is called a process, and typically a process can perform only one task at a time. If there are many tasks that need to be performed, there are three ways to solve them.

    1. Queuing. Because a process can only perform one task at a time, you have to wait for the previous task to finish and then perform the subsequent tasks.

    2. Creates a new process. Use the fork command to create a new process for each task.

    3. Creates a new thread. Because processes are too resource-intensive, today's programs often allow a process to contain multiple threads, which are performed by threads.

If a task is time-consuming, such as involving many I/O (input/output) operations, then the thread is probably running the following way.

The green part is the running time of the program, and the red part is the waiting time. As you can see, because I/O operations are slow, the majority of the running time of this thread is the result of the return of the I/O operations, such as empty. This operation is known as the "synchronous mode" (synchronous I/O).

If you are using multiple threads and running multiple tasks at the same time, it is probably the following.

Shows that multithreading not only occupies multiple times of system resources, but also is idle for many times the resources, which is obviously unreasonable.

The green part of the main thread, or the run time, and the orange portion indicates the idle time. Whenever I am encountering I/O, the main thread will let the event loop thread notify the appropriate I/O program and then run backwards, so there is no red wait time. When the I/O program finishes, the Event loop thread returns the result back to the main thread. The main thread calls the pre-set callback function to complete the task.

As you can see, because of the extra orange free time, the main thread is able to run more tasks, which improves efficiency. This mode of operation is called the "Asynchronous Pattern" (asynchronous I/O).

This is exactly how the JavaScript language works. The single-threaded model, although a great limitation on JavaScript, makes it an advantage that other languages do not. If the deployment is good, the JavaScript program is not blocked, which is why the node. JS platform can use very little resources to cope with the causes of high traffic access.

If you have a large number of asynchronous tasks (which is the case), they generate a lot of messages in Message Queuing. These messages line up and wait for the main thread to go. In essence, "Message Queuing" is a "FIFO" data structure. For example, a mouse click produces a series of messages (various events), events mousedown mouseup precede the event, and mouseup events click precede the event.

Reference links
    • John Dalziel, the race for speed part 2:how JavaScript compilers work
    • Jake Archibald,deep dive into the murky waters of script loading
    • Mozilla Developer Network, Window.settimeout
    • Remy Sharp, throttling function calls
    • Ayman Farhat, an alternative to Javascript ' s evil setinterval
    • Ilya Grigorik, script-injected "Async scripts" considered harmful
    • Axel Rauschmayer, ECMAScript 6 promises (in): Foundations
    • Daniel Imms, async vs Defer attributes
    • Craig Buckler, Load non-blocking JavaScript with HTML5 Async and Defer
    • Domenico De Felice, how browsers work

JS engine in-depth analysis-reprint

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.