Foreword
During the interview, a classic interview question is often encountered:
How do I optimize page load speed?
There is always one of the regular answers:
Put css files at the top of the page and js files at the bottom of the page.
So why put the js file at the very bottom of the page?
Let’s look at this code first:
<!DOCTYPE html> <html lang="en"> <head> <title>Hi</title> <script> console.log("Howdy~"); </script> <script src="//i2.wp.com/unpkg.com/[email protected]/dist/vue.global.js"></script> <script src="//i2.wp.com/unpkg.com/[email protected]/dist/vue-router.global.js"></script> </head> <body> Hello ~ </body> </html>
His order of execution is:
- Print on the console:
Howdy ~
- Request and execute vue.global.js
- Request and execute vue-router.global.js
- Display on the page:
Hello ~
- Trigger the DOMContentLoaded event
The parsing rule of the browser is: if a script
tag is encountered, the construction of the DOM
will be suspended, and the script
tag will be executed instead. If so, then The browser still needs to wait for it to be “downloaded” and “executed” before continuing to parse the subsequent HTML.
If it takes 3 seconds to request and execute “vue.global.js“, and “vue-router.global.js” takes 2 seconds, then the Hello in the page ~
, it will take at least 5 seconds to display.
It can be seen that the script tag will block the browser from parsing HTML. If you put script
in head
, in the case of poor network, the page will be in the White screen status.
A long time ago, these external scripts were usually placed at the end of the body
tag, making sure to parse and display the content in body
first, and then request execution one by one These outreach scripts.
Is there any other more elegant solution?
The answer is yes, now the script
tag adds two new attributes: defer
and async
, to solve such problems and improve page performance of.
First look at the explanation on MDN:
This boolean property is set to inform the browser that the script will execute after the document has finished parsing, but before firing the DOMContentLoaded event.
Scripts with the defer attribute block the DOMContentLoaded event until the script is loaded and parsed.
The document directly summarizes its features. Let's take a look at the following code first, expand and talk about the details, and deepen our understanding.
<!DOCTYPE html> <html lang="en"> <head> <title>Hi</title> <script> console.log("Howdy ~"); </script> <script defer src="//i2.wp.com/unpkg.com/vue@3.2.41/dist/vue.global.js"></script> <script defer src="//i2.wp.com/unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script> </head> <body> Hello ~ </body> </html>
His order of execution is:
- Print on the console:
Howdy ~
- Display on the page:
Hello ~
- Request and execute vue.global.js
- Request and execute vue-router.global.js
- Trigger DOMContentLoaded event
If the defer
attribute is set on the script
tag, then when the browser parses here, it will silently start downloading the script in the background and continue parsing the following HTML. Does not block parsing operations.
After the HTML parsing is completed, the browser will immediately execute the script downloaded in the background, and the DOMContentLoaded
event will be triggered only after the script execution is completed.
It seems to be quite understandable, right? Let's discuss 2 more small details:
Q1: What happens if the script with the defer
attribute set has not been downloaded after the HTML parsing is complete?
A1: The browser will wait for the script to be downloaded before executing the script, and then trigger the DOMContentLoaded
event after the execution is complete.
Q2: If there are multiple scripts with the defer
attribute set, how will the browser handle it?
A2: The browser will download these scripts in parallel in the background. After the HTML parsing is completed and all scripts are downloaded, they will be executed in the relative order in which they appear in the HTML. After all scripts are executed , and then trigger the DOMContentLoaded
event.
Best Practices:
It is recommended that all external scripts set this property by default, because it will not block HTML parsing, JavaScript resources can be downloaded in parallel, and can also be executed in their relative order in HTML to ensure that there will be no missing when scripts with dependencies are running rely.
In the application of SPA, you can consider adding defer
attribute to all script
tags, and put them at the end of body
. In modern browsers, it can be downloaded in parallel to increase the speed, and it can also ensure that in old browsers, the browser will not be blocked from parsing HTML, which will play a role in downgrading.
Note:
- The
defer
attribute is only applicable to external scripts, if thescript
script has nosrc
, thedefer
attribute will be ignored. - The
defer
attribute is invalid for module scripts (? ?? ?), because module scripts are loaded in the form ofdefer
.
By convention, first look at the explanation on MDN:
For normal scripts, if there is an async attribute, normal scripts will be requested in parallel, parsed and executed as soon as possible.
For module scripts, if the async attribute is present, the script and all its dependencies are executed on a deferred queue, so they are requested in parallel, parsed and executed as soon as possible.
This property eliminates parse-blocking Javascript.
Parsing blocking Javascript will cause the browser to load and execute the script before continuing to parse.
I feel that this description is quite clear, but let's take a look at the following code first, expand and talk about the details, and deepen our understanding.
<!DOCTYPE html> <html lang="en"> <head> <title>Hi</title> <script> console.log("Howdy ~"); </script> <script async src="//i2.wp.com/google-analytics.com/analytics.js"></script> <script async src="//i2.wp.com/ads.google.cn/ad.js"></script> </head> <body> Hello ~ </body> </html>
His order of execution is:
- Print on the console:
Howdy ~
- Parallel requests for analytics.js and ad.js
- Display on the page:
Hello ~
- According to the actual situation of the network, the following items will be executed out of order
- Execute analytics.js (execute immediately after downloading)
- Execute ad.js (execute immediately after downloading)
- Trigger the ? DOMContentLoaded ? event (possibly before, between, and after the above 2 scripts)
When the browser parses the script
tag with the async
attribute, it will not block the page, and it will also silently download the script in the background. When he finishes downloading, the browser will pause parsing HTML and execute this script immediately.
It seems to be quite understandable, right? Let's discuss 2 more small details:
Q1: What happens if the browser has not finished parsing the HTML after the script
with the async
attribute set is downloaded?
A1: The browser will pause parsing HTML, execute this script immediately, and continue parsing HTML after execution.
Q2: If there are multiple script
tags with async
attributes, will they be executed in code order after they are downloaded?
A2: No. The execution sequence is: whoever downloads first, executes first. async
is characterized by "complete independence" and does not depend on other content.
Best Practices:
This property can be used when our project needs to integrate other independent third-party libraries, they do not depend on us, and we do not depend on them.
It is a good optimization solution to let the browser download and execute it asynchronously by setting this property.
Note:
- The
async
attribute is only applicable to external scripts, if thescript
script has nosrc
, theasync
attribute will be ignored.
Summary
defer
- Do not block the browser from parsing HTML, and
script
will not be executed until the HTML is parsed. - JavaScript resources are downloaded in parallel.
- Scripts are executed in relative order from the HTML.
- The
DOMContentLoaded
event will not be triggered until the script is downloaded and executed. - During the execution of the script, the existing elements in the HTML must be obtained.
- The
defer
attribute has no effect on template scripts. - Applies to: All external scripts (
script
referenced viasrc
).
async
- Does not block the browser from parsing HTML, but after
script
is downloaded, it will immediately interrupt the browser from parsing HTML and execute thisscript
. - JavaScript resources are downloaded in parallel.
- Independent of each other, whoever downloads first will execute first, there is no fixed sequence, and it is uncontrollable.
- Since there is no definite execution timing, the existing elements in the HTML may not be obtained in the script.
- The
DOMContentLoaded
event has no correlation with thescript
script, and their sequence cannot be determined. - Applies to: Standalone third-party scripts.
Also: The biggest difference between async
and defer
is when they are executed.
One More Thing
Have you ever wondered how a browser would handle a script
tag with both defer
and async
set?
Let me talk about the conclusion first: From the perspective of expression, async
has a higher priority than defer
, that is, if these two attributes exist at the same time, the browser will use async
feature to load this script.
There are mainly 2 situations:
If it is a "normal script", the browser will first judge whether the async
attribute exists. If it exists, it will load the script with the async
attribute. If it does not exist, it will judge again Whether there is a defer
attribute.
If it is a "module script", the browser will determine whether the async
attribute exists:
- If it exists, the browser will download this module and all its dependent modules in parallel, and execute this script immediately after all downloads are completed.
- If it does not exist, the browser will also download this module and all its dependent modules in parallel, and then execute this script after the browser has parsed the HTML.
- Another thing to note: setting the
defer
attribute on a module script has no effect.
A picture is worth a thousand words
Finally, use a picture to summarize the loading mode of these two attributes: