BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE Interview Notes - Page Loading - Script Placement & defer/async Attributes

In-depth analysis of optimal JavaScript script tag placement and the timing and effects of defer and async attributes to help you enhance web performance and user experience.

影片縮圖

Lazy to read articles? Then watch videos!

Script Tag Placement

The loading and execution timing of JavaScript in a webpage directly affects user experience. The behavior of script tags varies depending on their placement:

  1. 1.
    Placed in <head>: The browser will download and execute JavaScript before parsing HTML, which blocks page rendering and slows down page loading.
  2. 2.
    Placed before </body>: The browser will render the HTML content first, then download and execute JavaScript. Users can see the page content faster, but some functionality may not be available before JavaScript loads.

Purpose of the defer attribute

The defer attribute allows the script to download alongside HTML parsing, but delays execution until HTML parsing is complete:

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

defer characteristics:

  • Does not block HTML parsing and rendering
  • Guarantees execution in the order specified in HTML
  • Executes before the DOMContentLoaded event
  • Only effective for external scripts (with src attribute)

Suitable scenarios:

  • Scripts that need to execute after the DOM is fully loaded
  • Scripts dependent on execution order
  • Scripts that don't need immediate execution

Purpose of module scripts

The type="module" attribute tells the browser to treat the script as a JavaScript module, supporting import/export syntax:

<script type="module" src="app.js"></script>

Differences between module and defer:

  • Module scripts have defer behavior by default, no need to add the defer attribute
  • Modules support import/export syntax, while defer scripts don't
  • Module scripts automatically use strict mode

Scenarios where module must be used instead of defer:

  • When import/export syntax is needed
  • When code needs to be modularized for better maintainability
// math.js (模組)
export function add(a, b) {
  return a + b;
}

// app.js (使用模組)
import { add } from './math.js';

document.addEventListener('DOMContentLoaded', () => {
  console.log(add(2, 3)); // 輸出: 5
});

Purpose of the async attribute

The async attribute allows the script to download alongside HTML parsing, and executes immediately after download completes:

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

async characteristics:

  • Does not block HTML parsing and rendering
  • Does not guarantee execution order; executes in download completion order
  • Pauses HTML parsing during execution
  • Only effective for external scripts
  • May not execute before the DOMContentLoaded event

Suitable scenarios:

  • Independent functionality that doesn't rely on other scripts
  • Third-party scripts like analytics and advertisements
  • Scripts that don't need to manipulate the DOM

Visual Comparison

The diagram below illustrates the behavioral differences between script loading methods:

──────────────────────────── Timeline ────────────────────────────>

【No Attribute - Before </body>】
HTML Parsing:  ■■■■■■■■■■■■■■■■■■■■■■■■■■HTML Parsing■■■■■■■■■■■■■■■■■■
               ↑                                      ↑
               Start                                 Complete
            
Script:                                             ■■■Download+Execute■■■
                                                    ↑                   ↑
                                                    Start               Complete
<body>
  <!-- Content -->
  <script src="script.js"></script>  <!-- Traditional approach -->
</body>

--------------------------------

【defer】
HTML Parsing:  ■■■■■■■■■■■■■■■■■■■■■■■■■■HTML Parsing■■■■■■■■■■■■■■■■■■→DOM Ready
               ↑                                      ↑            ↑
               Start                                 Complete      DOMContentLoaded
            
Script:        ■■■■■■■■■■■■■Download■■■■■■■■■■■■■             ■■■Execute■■■
               ↑                              ↑                   ↑          ↑
               Start                          Complete            Start      Complete
            
<head>
  <script defer src="script.js"></script>  <!-- Modern recommended approach -->
</head>

--------------------------------

【async】
HTML Parsing:  ■■■■■■■■■■■■■■■■■Pause■■■■■■■■■Remaining Parse■■■■■■■■■■■
               ↑                    ↑         ↑                  ↑
               Start               Pause      Resume             Complete
            
Script:        ■■■■■■■Download■■■■■■■■■Execute■■■
               ↑                  ↑    ↑
               Start             Complete Execute Complete
            
<head>
  <script async src="script.js"></script>  <!-- Suitable for independent scripts -->
</head>

Best Practices

  1. 1.
    Modern recommended approach: Place scripts with the defer attribute in the <head>
    <head>
      <script defer src="main.js"></script>
    </head>

    This allows scripts to start downloading early while not blocking HTML parsing, improving the page loading experience.

  2. 2.
    Independent scripts: Use the async attribute
    <head>
      <script async src="analytics.js"></script>
    </head>
  3. 3.
    Traditional approach: Place scripts before </body>
    <body>
      <!-- 頁面內容 -->
      <script src="main.js"></script>
    </body>
  4. 4.
    Core functionality and time-sensitive: No attributes, directly in <head>
    <head>
      <script src="critical.js"></script>
    </head>

🔥 Common Interview Questions

(1) What's the difference between defer and async?

Answer: The main differences are in execution timing and order:

  • defer: Executes after HTML parsing is complete, in the order specified in the HTML
  • async: Executes immediately after download completion, doesn't guarantee order, interrupts HTML parsing
<!-- defer example: executed in order -->
<head>
  <script defer src="first.js"></script>
  <script defer src="second.js"></script>
  <!-- first.js will always execute before second.js -->
</head>

<!-- async example: order not guaranteed -->
<head>
  <script async src="analytics.js"></script>
  <script async src="ads.js"></script>
  <!-- whichever downloads first executes first -->
</head>

(2) Why is it not recommended to place all scripts in <head> without using defer or async?

Answer: This forces the browser to download and execute all JavaScript before rendering the page, resulting in extended white screen time, severely impacting First Contentful Paint (FCP) and user experience.

<!-- Not recommended: blocks rendering -->
<head>
  <script src="large-library.js"></script>
  <script src="app.js"></script>
  <script src="components.js"></script>
  <!-- Page waits for all scripts to download and execute before rendering -->
</head>

<!-- Recommended: use defer -->
<head>
  <script defer src="large-library.js"></script>
  <script defer src="app.js"></script>
  <script defer src="components.js"></script>
  <!-- HTML parsing isn't blocked, scripts execute before DOMContentLoaded -->
</head>

(3) When to choose defer, and when to choose async?

Answer:

  • Choose defer: When scripts need to manipulate the DOM or depend on execution order
  • Choose async: When scripts are completely independent and order doesn't matter, like analytics tools or ads
<!-- Use defer when: DOM and execution order are needed -->
<head>
  <script defer src="jquery.js"></script>
  <script defer src="jquery-plugin.js"></script>
  <script defer src="app.js">
    // This script depends on jquery and jquery-plugin
    // and may need to manipulate DOM elements
  </script>
</head>

<!-- Use async when: functionality is independent -->
<head>
  <script async src="google-analytics.js"></script>
  <script async src="facebook-pixel.js"></script>
  <!-- These scripts are independent, don't need specific order, and don't rely on DOM -->
</head>

(4) How does module script (type="module") loading behavior work?

Answer: Module scripts have defer behavior by default, even without explicitly setting the defer attribute. If async behavior is desired, the async attribute must be explicitly added.

<!-- Has defer behavior by default -->
<head>
  <script type="module" src="app.js"></script>
  <!-- Equivalent to <script type="module" defer src="app.js"></script> -->
  
  <!-- If async behavior is needed, must be explicitly specified -->
  <script type="module" async src="independent-module.js"></script>
</head>

(5) Can inline scripts use defer or async attributes?

Answer: No. The defer and async attributes are only effective for external scripts (script tags with a src attribute) and have no effect on inline scripts.

<!-- Invalid: defer and async don't work on inline scripts -->
<head>
  <script defer>
    console.log("This defer attribute has no effect");
    // This script executes immediately, blocking HTML parsing
  </script>
  
  <script async>
    console.log("This async attribute has no effect");
    // This script also executes immediately
  </script>
  
  <!-- Valid: external scripts can use defer or async -->
  <script defer src="external.js"></script>
</head>