Skip to content

Commit

Permalink
What is JavaScript: use JS module instead of async/defer/DOMContentLo…
Browse files Browse the repository at this point in the history
…aded
  • Loading branch information
mb21 committed Jul 20, 2024
1 parent cbf5e52 commit 3d15dae
Showing 1 changed file with 22 additions and 104 deletions.
126 changes: 22 additions & 104 deletions files/en-us/learn/javascript/first_steps/what_is_javascript/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,6 @@ Let's briefly recap the story of what happens when you load a web page in a brow
![HTML, CSS and JavaScript code come together to create the content in the browser tab when the page is loaded](execution.png)

A very common use of JavaScript is to dynamically modify HTML and CSS to update a user interface, via the Document Object Model API (as mentioned above).
Note that the code in your web documents is generally loaded and executed in the order it appears on the page.
Errors may occur if JavaScript is loaded and run before the HTML and CSS that it is intended to modify.
You will learn ways around this later in the article, in the [Script loading strategies](#script_loading_strategies) section.

### Browser security

Expand Down Expand Up @@ -211,37 +208,37 @@ Whereas CSS uses {{htmlelement("link")}} elements to apply external stylesheets

1. First of all, make a local copy of our example file [apply-javascript.html](https://github.com/mdn/learning-area/blob/main/javascript/introduction-to-js-1/what-is-js/apply-javascript.html). Save it in a directory somewhere sensible.
2. Open the file in your web browser and in your text editor. You'll see that the HTML creates a simple web page containing a clickable button.
3. Next, go to your text editor and add the following in your head — just before your closing `</head>` tag:
3. Next, go to your text editor and add the following at the bottom of your body — just before your closing `</body>` tag:

```html
<script>
// JavaScript goes here
</script>
```

Note that the code in your web documents is generally loaded and executed in the order it appears on the page. By placing the JavaScript at the bottom, we ensure that all HTML elements are loaded. (See also [Script loading strategies](#script_loading_strategies) below.)

4. Now we'll add some JavaScript inside our {{htmlelement("script")}} element to make the page do something more interesting — add the following code just below the "// JavaScript goes here" line:

```js
document.addEventListener("DOMContentLoaded", () => {
function createParagraph() {
const para = document.createElement("p");
para.textContent = "You clicked the button!";
document.body.appendChild(para);
}

const buttons = document.querySelectorAll("button");

for (const button of buttons) {
button.addEventListener("click", createParagraph);
}
});
function createParagraph() {
const para = document.createElement("p");
para.textContent = "You clicked the button!";
document.body.appendChild(para);
}

const buttons = document.querySelectorAll("button");

for (const button of buttons) {
button.addEventListener("click", createParagraph);
}
```

5. Save your file and refresh the browser — now you should see that when you click the button, a new paragraph is generated and placed below.

> **Note:** If your example doesn't seem to work, go through the steps again and check that you did everything right.
> Did you save your local copy of the starting code as a `.html` file?
> Did you add your {{htmlelement("script")}} element just before the `</head>` tag?
> Did you add your {{htmlelement("script")}} element just before the `</body>` tag?
> Did you enter the JavaScript exactly as shown? **JavaScript is case sensitive, and very fussy, so you need to enter the syntax exactly as shown, otherwise it may not work.**
> **Note:** You can see this version on GitHub as [apply-javascript-internal.html](https://github.com/mdn/learning-area/blob/main/javascript/introduction-to-js-1/what-is-js/apply-javascript-internal.html) ([see it live too](https://mdn.github.io/learning-area/javascript/introduction-to-js-1/what-is-js/apply-javascript-internal.html)).
Expand All @@ -251,10 +248,10 @@ Whereas CSS uses {{htmlelement("link")}} elements to apply external stylesheets
This works great, but what if we wanted to put our JavaScript in an external file? Let's explore this now.

1. First, create a new file in the same directory as your sample HTML file. Call it `script.js` — make sure it has that .js filename extension, as that's how it is recognized as JavaScript.
2. Replace your current {{htmlelement("script")}} element with the following:
2. Remove your current {{htmlelement("script")}} element at the bottom of the `</body>` and add the following just before the closing `</head>` tag (that way the browser can start loading the file sooner than when it's at the bottom):

```html
<script src="script.js" defer></script>
<script type="module" src="script.js"></script>
```

3. Inside `script.js`, add the following script:
Expand Down Expand Up @@ -329,95 +326,16 @@ The JavaScript does not need to be changed.
### Script loading strategies

There are a number of issues involved with getting scripts to load at the right time. Nothing is as simple as it seems!
A common problem is that all the HTML on a page is loaded in the order in which it appears.
All the HTML on a page is loaded in the order in which it appears.
If you are using JavaScript to manipulate elements on the page (or more accurately, the [Document Object Model](/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents#the_document_object_model)), your code won't work if the JavaScript is loaded and parsed before the HTML you are trying to do something to.

In the above code examples, in the internal and external examples the JavaScript is loaded and run in the head of the document, before the HTML body is parsed.
This could cause an error, so we've used some constructs to get around it.

In the internal example, you can see this structure around the code:

```js
document.addEventListener("DOMContentLoaded", () => {
//
});
```

This is an event listener, which listens for the browser's `DOMContentLoaded` event, which signifies that the HTML body is completely loaded and parsed.
The JavaScript inside this block will not run until after that event is fired, therefore the error is avoided (you'll [learn about events](/en-US/docs/Learn/JavaScript/Building_blocks/Events) later in the course).

In the external example, we use a more modern JavaScript feature to solve the problem, the `defer` attribute, which tells the browser to continue downloading the HTML content once the `<script>` tag element has been reached.

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

In this case both the script and the HTML will load simultaneously and the code will work.

> **Note:** In the external case, we did not need to use the `DOMContentLoaded` event because the `defer` attribute solved the problem for us.
> We didn't use the `defer` solution for the internal JavaScript example because `defer` only works for external scripts.
An old-fashioned solution to this problem used to be to put your script element right at the bottom of the body (e.g. just before the `</body>` tag), so that it would load after all the HTML has been parsed.
The problem with this solution is that loading/parsing of the script is completely blocked until the HTML DOM has been loaded.
On larger sites with lots of JavaScript, this can cause a major performance issue, slowing down your site.

#### async and defer

There are actually two modern features we can use to bypass the problem of the blocking script — `async` and `defer` (which we saw above).
Let's look at the difference between these two.

Scripts loaded using the `async` attribute will download the script without blocking the page while the script is being fetched.
However, once the download is complete, the script will execute, which blocks the page from rendering. This means that the rest of the content on the web page is prevented from being processed and displayed to the user until the script finishes executing.
You get no guarantee that scripts will run in any specific order.
It is best to use `async` when the scripts in the page run independently from each other and depend on no other script on the page.

Scripts loaded with the `defer` attribute will load in the order they appear on the page.
They won't run until the page content has all loaded, which is useful if your scripts depend on the DOM being in place (e.g. they modify one or more elements on the page).

Here is a visual representation of the different script loading methods and what that means for your page:

![How the three script loading method work: default has parsing blocked while JavaScript is fetched and executed. With async, the parsing pauses for execution only. With defer, parsing isn't paused, but execution on happens after everything is else is parsed.](async-defer.jpg)

_This image is from the [HTML spec](https://html.spec.whatwg.org/images/asyncdefer.svg), copied and cropped to a reduced version, under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license terms._

For example, if you have the following script elements:

```html
<script async src="js/vendor/jquery.js"></script>

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

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

You can't rely on the order the scripts will load in.
`jquery.js` may load before or after `script2.js` and `script3.js` and if this is the case, any functions in those scripts depending on `jquery` will produce an error because `jquery` will not be defined at the time the script runs.

`async` should be used when you have a bunch of background scripts to load in, and you just want to get them in place as soon as possible.
For example, maybe you have some game data files to load, which will be needed when the game actually begins, but for now you just want to get on with showing the game intro, titles, and lobby, without them being blocked by script loading.

Scripts loaded using the `defer` attribute (see below) will run in the order they appear in the page and execute them as soon as the script and content are downloaded:

```html
<script defer src="js/vendor/jquery.js"></script>

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

<script defer src="js/script3.js"></script>
```
There are a few different strategies to make sure your JavaScript only runs after the HTML is parsed:

In the second example, we can be sure that `jquery.js` will load before `script2.js` and `script3.js` and that `script2.js` will load before `script3.js`.
They won't run until the page content has all loaded, which is useful if your scripts depend on the DOM being in place (e.g. they modify one or more elements on the page).
- In the internal JavaScript example above, the script element is placed at the bottom of the body of the document, and therefore only run after the rest of the HTML body is parsed.

To summarize:
- In the external JavaScript example above, the script element is placed in the head of the document, before the HTML body is parsed. But because we're using `<script type="module">`, the code is treated as a [module](/en-US/docs/Web/JavaScript/Guide/Modules) and the browser waits for all HTML to be processed before executing JavaScript modules.

- `async` and `defer` both instruct the browser to download the script(s) in a separate thread, while the rest of the page (the DOM, etc.) is downloading, so the page loading is not blocked during the fetch process.
- scripts with an `async` attribute will execute as soon as the download is complete.
This blocks the page and does not guarantee any specific execution order.
- scripts with a `defer` attribute will load in the order they are in and will only execute once everything has finished loading.
- If your scripts should be run immediately and they don't have any dependencies, then use `async`.
- If your scripts need to wait for parsing and depend on other scripts and/or the DOM being in place, load them using `defer` and put their corresponding `<script>` elements in the order you want the browser to execute them.
- Before browsers supported JavaScript modules, placing a script element in the head of the document could block the whole HTML of the page from displaying before the script was loaded. So we had to add `defer` and `async` attributes on the {{htmlelement("script")}} element, and then that could cause errors when the JavaScript was run but the HTML was not parsed yet. So we had to use the [DOMContentLoaded event](/en-US/docs/Web/API/Document/DOMContentLoaded_event). But unless you need to support very old browsers, don't do this, and simply use `<script type="module>` instead.

## Comments

Expand Down

0 comments on commit 3d15dae

Please sign in to comment.