In modern websites, scripts are often “heavier” than HTML: their download size is larger, and processing time is also longer.
When the browser loads HTML and comes across a
<script>...</script> tag, it can’t continue building DOM. It must execute the script right now. The same happens for external scripts
<script src="..."></script>: the browser must wait until the script downloads, execute it, and only after process the rest of the page.
That leads to two important issues:
- Scripts can’t see DOM elements below them, so can’t add handlers etc.
- If there’s a bulky script at the top of the page, it “blocks the page”. Users can’t see the page content till it downloads and runs:
There are some workarounds to that. For instance, we can put a script at the bottom of the page. Then it can see elements above it, and it doesn’t block the page content from showing:
But this solution is far from perfect. For example, the browser notices the script (and can start downloading it) only after it downloaded the full HTML document. For long HTML documents, that may be a noticeable delay.
Such things are invisible for people using very fast connections, but many people in the world still have slower internet speeds and use far-from-perfect mobile internet.
Luckily, there are two
<script> attributes that solve the problem for us:
defer attribute tells the browser that it should go on working with the page, and load the script “in background”, then run the script when it loads.
Here’s the same example as above, but with
- Scripts with
defernever block the page.
- Scripts with
deferalways execute when the DOM is ready, but before
The following example demonstrates that:
- The page content shows up immediately.
DOMContentLoadedwaits for the deferred script. It only triggers when the script
(2)is downloaded is executed.
Deferred scripts keep their relative order, just like regular scripts.
So, if we have a long script first, and then a smaller one, then the latter one waits.
Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The
small.js probably makes it first.
But the specification requires scripts to execute in the document order, so it waits for
long.js to execute.
deferattribute is only for external scripts
defer attribute is ignored if the
<script> tag has no
async attribute means that a script is completely independent:
- The page doesn’t wait for async scripts, the contents is processed and displayed.
DOMContentLoadedand async scripts don’t wait each other:
DOMContentLoadedmay happen both before an async script (if an async script finishes loading after the page is complete)
- …or after an async script (if an async script is short or was in HTTP-cache)
- Other scripts don’t wait for
asyncscripts don’t wait for them.
So, if we have several
async scripts, they may execute in any order. Whatever loads first – runs first:
- The page content shows up immediately:
asyncdoesn’t block it.
DOMContentLoadedmay happen both before and after
async, no guarantees here.
- Async scripts don’t wait for each other. A smaller script
small.jsgoes second, but probably loads before
long.js, so runs first. That’s called a “load-first” order.
Async scripts are great when we integrate an independent third-party script into the page: counters, ads and so on, as they don’t depend on our scripts, and our scripts shouldn’t wait for them:
<!-- Google Analytics is usually added like this --> <script async src="https://google-analytics.com/analytics.js"></script>
The script starts loading as soon as it’s appended to the document
Dynamic scripts behave as “async” by default.
- They don’t wait for anything, nothing waits for them.
- The script that loads first – runs first (“load-first” order).
We can change the load-first order into the document order (just like regular scripts) by explicitly setting
async property to
For example, here we add two scripts. Without
script.async=false they would execute in load-first order (the
small.js probably first). But with that flag the order is “as in the document”:
defer have one common thing: downloading of such scripts doesn’t block page rendering. So the user can read page content and get acquanted with the page immediately.
But there are also essential differences between them:
||Load-first order. Their document order doesn’t matter – which loads first||Irrelevant. May load and execute while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough.|
||Document order (as they go in the document).||Execute after the document is loaded and parsed (they wait if needed), right before
Please note that if you’re using
defer, then the page is visible before the script loads.
So the user may read the page, but some graphical components are probably not ready yet.
There should be “loading” indication in proper places, not-working buttons disabled, to clearly show the user what’s ready and what’s not.
defer is used for scripts that need the whole DOM and/or their relative execution order is important. And
async is used for independent scripts, like counters or ads. And their relative execution order does not matter.