To solve the problem of scripting file download blocking page rendering, one way is to add the defer attribute.
Async PropertyAnother way to solve the "blocking effect" is to add async
attributes.
<script src="1.js"async></script><script src="2.js"async></script>
async
The function of a property is to download the script using another process that does not block rendering when it is downloaded.
- Browser starts parsing HTML page
- During parsing,
async
labels with attributes are found script
- The browser continues to parse the HTML page and simultaneously downloads
script
the external script in the tag
- The script download completes, the browser pauses parsing the HTML page, starts to execute the downloaded script
- The browser resumes parsing the HTML Web page after the script is executed
async
Property 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
.
defer
Which 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 redrawThe 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 scriptsIn 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 usedIf 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 machineJavaScript 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:
- Read the code, parse it (Lexical analysis), and break the code down into lexical elements (tokens).
- The word meta is parsed (parsing) and the code is organized into a "syntax tree" (syntax).
- Using the Translator (translator), convert the code to bytecode (bytecode).
- 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 meaningFirst, 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 QueuingIn 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 LoopThe 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.
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.
Creates a new process. Use the fork command to create a new process for each task.
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