JavaScript scripts are introduced into a large number of web pages, which are usually larger and heavier than HTML, and therefore take longer to process. When the browser encounters a <script>...</script>
tag while loading HTML, the browser cannot continue building the DOM and must wait for the JavaScript script to finish executing. The same is true for external scripts <script src="..."></script>
: the browser must wait for the script to download and complete its execution before continuing to process the rest of the page.
This leads to two important problems:
- Scripts cannot access DOM elements below them, scripts cannot add handlers to them, etc.
- If there is a clunky script at the top of the page, it “blocks the page”. The user cannot see the content of the page until the script is downloaded and executed.
A common practice is to place the JavaScript script at the end of the page so that it can access the elements above it without blocking the page’s display content. But this solution is far from perfect. For example, the browser won’t notice the script (and can start downloading it) until the full HTML document has been downloaded. For long HTML documents, this can cause significant delays.
That’s not a big deal for people using high-speed connections, who won’t experience this lag. But there are still many parts of the world where people use slow networks, especially mobile networks in some areas.
Fortunately, there are defer
and async
two <script>
features that can solve this problem.
defer feature
defer
The attribute tells the browser not to wait for the script, the browser will continue processing the HTML, building the DOM. JavaScript scripts are downloaded “in the background” and then executed when the DOM is built.
<!doctype html>
<body>
<p>...content before script...</p>
<script defer src="https://zhanzhbkzzz.com/test.js"></script>
<!-- The following is immediately visible -->
<p>...The defer application example...</p>
</body>
in other words:
defer
Scripts with features do not block the page.- Scripts with
defer
properties always wait until the DOM has been parsed, but areDOMContentLoaded
executed before events.
The following example will explain what the second sentence means:
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
</script>
<script defer src="https://bkzzz.com/test.js"></script>
<p>...The contents of the script are immediately displayed...</p>
- The page content is displayed immediately.
DOMContentLoaded
The event handler waits fordefer
the script with the attribute to finish executing. It will only be triggered after the script is downloaded and its execution has ended.
Scripts with defer
attributes maintain their relative order, just like regular scripts.
Suppose, we have two defer
scripts with characteristics: test1.js
before and test2.js
after.
<script defer src="https://bkzzz.com/test1.js"></script>
<script defer src="https://bkzzz.com/test2.js"></script>
The browser scans the page for scripts and downloads them in parallel to improve performance. So in the example above, the two scripts are downloaded in parallel. test2.js
may be downloaded first.
However, in defer
addition to telling the browser “don’t block the page”, features also ensure the relative order of script execution. Even if test2.js
it is loaded first, it test1.js
will not be executed until the end of execution.
This can be useful when we need to load a JavaScript library before loading scripts that depend on it.
Note: defer
Features are only available for external scripts . If the <script>
script doesn’t src
, the attribute is ignored defer
and has no effect for inline scripts.
async feature
async
Features are defer
somewhat similar. It also enables scripts not to block the page. However, there are important differences in behavior.
async
Features mean that scripts are completely self-contained:
- Browsers don’t
async
block with scripts (anddefer
similar). - Other scripts don’t wait for
async
scripts to load to finish, and similarly,async
scripts don’t wait for other scripts. DOMContentLoaded
and async scripts don’t wait for each other:DOMContentLoaded
May happen before the async script (if the async script doesn’t load until the page is done)DOMContentLoaded
Can also happen after an async script (if the async script is short, or loaded from the HTTP cache)
In other words, the async
script loads in the background and runs when it is ready. The DOM and other scripts don’t wait for them, and they don’t wait for anything else. async
A script is a completely separate script that is executed when the load is complete.
async will not execute in script order, whoever loads first will execute it first.
Loading asynchronously is great when we’re integrating independent third-party scripts into the page: counters, ads, etc., because they don’t depend on our script, and our script shouldn’t wait for them:
<!-- Google Analytics Scripts are typically embedded in pages like this -->
<script async src="https://google-analytics.com/analytics.js"></script>
Summarize
async
and defer
have one thing in common: loading such a script will not block the rendering of the page. Therefore, users can immediately read and understand the page content.
However, there are also some essential differences between them:
order | DOMContentLoaded | |
---|---|---|
async | Load priority order . The order of scripts in the document does not matter – the ones loaded first are executed first | irrelevant. Possibly loaded and executed before the document is loaded. This can happen if the script is small or from a cache and the document is long enough. |
defer | Document order (their order in the document) | Executed after the document has been loaded and parsed (and waited if needed), i.e. DOMContentLoaded before. |
In actual development, defer
used for scripts that require the entire DOM, and/or when the relative execution order of scripts is important.
async
For stand-alone scripts, such as counters or advertisements, where the relative execution order of these scripts does not matter.