Merge branch 'refactor'
|
@ -57,7 +57,7 @@ Even if we press `key:Shift+Enter` to input multiple lines, and put `use strict`
|
||||||
|
|
||||||
The reliable way to ensure `use strict` would be to input the code into console like this:
|
The reliable way to ensure `use strict` would be to input the code into console like this:
|
||||||
|
|
||||||
```
|
```js
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
```js run no-beautify
|
```js run no-beautify
|
||||||
function sortByName(arr) {
|
function sortByAge(arr) {
|
||||||
arr.sort((a, b) => a.age > b.age ? 1 : -1);
|
arr.sort((a, b) => a.age > b.age ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@ let john = { name: "John", age: 25 };
|
||||||
let pete = { name: "Pete", age: 30 };
|
let pete = { name: "Pete", age: 30 };
|
||||||
let mary = { name: "Mary", age: 28 };
|
let mary = { name: "Mary", age: 28 };
|
||||||
|
|
||||||
let arr = [ john, pete, mary ];
|
let arr = [ pete, john, mary ];
|
||||||
|
|
||||||
sortByName(arr);
|
sortByAge(arr);
|
||||||
|
|
||||||
// now sorted is: [john, mary, pete]
|
// now sorted is: [john, mary, pete]
|
||||||
alert(arr[0].name); // John
|
alert(arr[0].name); // John
|
||||||
|
alert(arr[1].name); // Mary
|
||||||
alert(arr[2].name); // Pete
|
alert(arr[2].name); // Pete
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,9 +2,9 @@ importance: 5
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Sort objects
|
# Sort users by age
|
||||||
|
|
||||||
Write the function `sortByName(users)` that gets an array of objects with the `age` property and sorts them by `age`.
|
Write the function `sortByAge(users)` that gets an array of objects with the `age` property and sorts them by `age`.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
|
@ -13,11 +13,12 @@ let john = { name: "John", age: 25 };
|
||||||
let pete = { name: "Pete", age: 30 };
|
let pete = { name: "Pete", age: 30 };
|
||||||
let mary = { name: "Mary", age: 28 };
|
let mary = { name: "Mary", age: 28 };
|
||||||
|
|
||||||
let arr = [ john, pete, mary ];
|
let arr = [ pete, john, mary ];
|
||||||
|
|
||||||
sortByName(arr);
|
sortByAge(arr);
|
||||||
|
|
||||||
// now: [john, mary, pete]
|
// now: [john, mary, pete]
|
||||||
alert(arr[0].name); // John
|
alert(arr[0].name); // John
|
||||||
|
alert(arr[1].name); // Mary
|
||||||
alert(arr[2].name); // Pete
|
alert(arr[2].name); // Pete
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,7 +5,7 @@ let i = 0;
|
||||||
|
|
||||||
let start = Date.now();
|
let start = Date.now();
|
||||||
|
|
||||||
let timer = setInterval(count, 0);
|
let timer = setInterval(count);
|
||||||
|
|
||||||
function count() {
|
function count() {
|
||||||
|
|
||||||
|
@ -20,4 +20,3 @@ function count() {
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ function count() {
|
||||||
if (i == 1000000000) {
|
if (i == 1000000000) {
|
||||||
alert("Done in " + (Date.now() - start) + 'ms');
|
alert("Done in " + (Date.now() - start) + 'ms');
|
||||||
} else {
|
} else {
|
||||||
setTimeout(count, 0);
|
setTimeout(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// a piece of heavy job
|
// a piece of heavy job
|
||||||
|
|
|
@ -15,7 +15,7 @@ These methods are not a part of JavaScript specification. But most environments
|
||||||
The syntax:
|
The syntax:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let timerId = setTimeout(func|code, delay[, arg1, arg2...])
|
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
@ -25,7 +25,7 @@ Parameters:
|
||||||
Usually, that's a function. For historical reasons, a string of code can be passed, but that's not recommended.
|
Usually, that's a function. For historical reasons, a string of code can be passed, but that's not recommended.
|
||||||
|
|
||||||
`delay`
|
`delay`
|
||||||
: The delay before run, in milliseconds (1000 ms = 1 second).
|
: The delay before run, in milliseconds (1000 ms = 1 second), by default 0.
|
||||||
|
|
||||||
`arg1`, `arg2`...
|
`arg1`, `arg2`...
|
||||||
: Arguments for the function (not supported in IE9-)
|
: Arguments for the function (not supported in IE9-)
|
||||||
|
@ -110,7 +110,7 @@ For browsers, timers are described in the [timers section](https://www.w3.org/TR
|
||||||
The `setInterval` method has the same syntax as `setTimeout`:
|
The `setInterval` method has the same syntax as `setTimeout`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let timerId = setInterval(func|code, delay[, arg1, arg2...])
|
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
|
||||||
```
|
```
|
||||||
|
|
||||||
All arguments have the same meaning. But unlike `setTimeout` it runs the function not only once, but regularly after the given interval of time.
|
All arguments have the same meaning. But unlike `setTimeout` it runs the function not only once, but regularly after the given interval of time.
|
||||||
|
@ -238,7 +238,7 @@ There's a side-effect. A function references the outer lexical environment, so,
|
||||||
|
|
||||||
## setTimeout(...,0)
|
## setTimeout(...,0)
|
||||||
|
|
||||||
There's a special use case: `setTimeout(func, 0)`.
|
There's a special use case: `setTimeout(func, 0)`, or just `setTimeout(func)`.
|
||||||
|
|
||||||
This schedules the execution of `func` as soon as possible. But scheduler will invoke it only after the current code is complete.
|
This schedules the execution of `func` as soon as possible. But scheduler will invoke it only after the current code is complete.
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ So the function is scheduled to run "right after" the current code. In other wor
|
||||||
For instance, this outputs "Hello", then immediately "World":
|
For instance, this outputs "Hello", then immediately "World":
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
setTimeout(() => alert("World"), 0);
|
setTimeout(() => alert("World"));
|
||||||
|
|
||||||
alert("Hello");
|
alert("Hello");
|
||||||
```
|
```
|
||||||
|
@ -303,7 +303,7 @@ function count() {
|
||||||
if (i == 1e9) {
|
if (i == 1e9) {
|
||||||
alert("Done in " + (Date.now() - start) + 'ms');
|
alert("Done in " + (Date.now() - start) + 'ms');
|
||||||
} else {
|
} else {
|
||||||
setTimeout(count, 0); // schedule the new call (**)
|
setTimeout(count); // schedule the new call (**)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,7 @@ function count() {
|
||||||
|
|
||||||
// move the scheduling at the beginning
|
// move the scheduling at the beginning
|
||||||
if (i < 1e9 - 1e6) {
|
if (i < 1e9 - 1e6) {
|
||||||
setTimeout(count, 0); // schedule the new call
|
setTimeout(count); // schedule the new call
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -371,8 +371,8 @@ setTimeout(function run() {
|
||||||
times.push(Date.now() - start); // remember delay from the previous call
|
times.push(Date.now() - start); // remember delay from the previous call
|
||||||
|
|
||||||
if (start + 100 < Date.now()) alert(times); // show the delays after 100ms
|
if (start + 100 < Date.now()) alert(times); // show the delays after 100ms
|
||||||
else setTimeout(run, 0); // else re-schedule
|
else setTimeout(run); // else re-schedule
|
||||||
}, 0);
|
});
|
||||||
|
|
||||||
// an example of the output:
|
// an example of the output:
|
||||||
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
|
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
|
||||||
|
@ -430,7 +430,7 @@ And if we use `setTimeout` to split it into pieces then changes are applied in-b
|
||||||
} while (i % 1e3 != 0);
|
} while (i % 1e3 != 0);
|
||||||
|
|
||||||
if (i < 1e9) {
|
if (i < 1e9) {
|
||||||
setTimeout(count, 0);
|
setTimeout(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,7 +226,7 @@ You may want skip those for now if you're reading for the first time, or if you
|
||||||
|
|
||||||
### Module scripts are deferred
|
### Module scripts are deferred
|
||||||
|
|
||||||
Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:onload-ondomcontentloaded)), for both external and inline scripts.
|
Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:script-async-defer)), for both external and inline scripts.
|
||||||
|
|
||||||
In other words:
|
In other words:
|
||||||
- external module scripts `<script type="module" src="...">` don't block HTML processing.
|
- external module scripts `<script type="module" src="...">` don't block HTML processing.
|
||||||
|
|
249
10-misc/12-mutation-observer/article.md
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
|
||||||
|
# Mutation observer
|
||||||
|
|
||||||
|
`MutationObserver` is a built-in object that observes a DOM element and fires a callback in case of changes.
|
||||||
|
|
||||||
|
We'll first see syntax, and then explore a real-world use case.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
`MutationObserver` is easy to use.
|
||||||
|
|
||||||
|
First, we create an observer with a callback-function:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let observer = new MutationObserver(callback);
|
||||||
|
```
|
||||||
|
|
||||||
|
And then attach it to a DOM node:
|
||||||
|
|
||||||
|
```js
|
||||||
|
observer.observe(node, config);
|
||||||
|
```
|
||||||
|
|
||||||
|
`config` is an object with boolean options "what kind of changes to react on":
|
||||||
|
- `childList` -- changes in the direct children of `node`,
|
||||||
|
- `subtree` -- in all descendants of `node`,
|
||||||
|
- `attributes` -- attributes of `node`,
|
||||||
|
- `attributeOldValue` -- record the old value of attribute (infers `attributes`),
|
||||||
|
- `characterData` -- whether to observe `node.data` (text content),
|
||||||
|
- `characterDataOldValue` -- record the old value of `node.data` (infers `characterData`),
|
||||||
|
- `attributeFilter` -- an array of attribute names, to observe only selected ones.
|
||||||
|
|
||||||
|
Then after any changes, the `callback` is executed, with a list of [MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects as the first argument, and the observer itself as the second argument.
|
||||||
|
|
||||||
|
[MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects have properties:
|
||||||
|
|
||||||
|
- `type` -- mutation type, one of
|
||||||
|
- `"attributes"` (attribute modified)
|
||||||
|
- `"characterData"` (data modified)
|
||||||
|
- `"childList"` (elements added/removed),
|
||||||
|
- `target` -- where the change occured: an element for "attributes", or text node for "characterData", or an element for a "childList" mutation,
|
||||||
|
- `addedNodes/removedNodes` -- nodes that were added/removed,
|
||||||
|
- `previousSibling/nextSibling` -- the previous and next sibling to added/removed nodes,
|
||||||
|
- `attributeName/attributeNamespace` -- the name/namespace (for XML) of the changed attribute,
|
||||||
|
- `oldValue` -- the previous value, only for attribute or text changes.
|
||||||
|
|
||||||
|
|
||||||
|
For example, here's a `<div>` with `contentEditable` attribute. That attribute allows us to focus on it and edit.
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<div contentEditable id="elem">Edit <b>me</b>, please</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let observer = new MutationObserver(mutationRecords => {
|
||||||
|
console.log(mutationRecords); // console.log(the changes)
|
||||||
|
});
|
||||||
|
observer.observe(elem, {
|
||||||
|
// observe everything except attributes
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
characterDataOldValue: true
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
If we change the text inside `<b>me</b>`, we'll get a single mutation:
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutationRecords = [{
|
||||||
|
type: "characterData",
|
||||||
|
oldValue: "me",
|
||||||
|
target: <text node>,
|
||||||
|
// other properties empty
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
If we select and remove the `<b>me</b>` altogether, we'll get multiple mutations:
|
||||||
|
|
||||||
|
```js
|
||||||
|
mutationRecords = [{
|
||||||
|
type: "childList",
|
||||||
|
target: <div#elem>,
|
||||||
|
removedNodes: [<b>],
|
||||||
|
nextSibling: <text node>,
|
||||||
|
previousSibling: <text node>
|
||||||
|
// other properties empty
|
||||||
|
}, {
|
||||||
|
type: "characterData"
|
||||||
|
target: <text node>
|
||||||
|
// ...details depend on how the browser handles the change
|
||||||
|
// it may coalesce two adjacent text nodes "Edit " and ", please" into one node
|
||||||
|
// or it can just delete the extra space after "Edit".
|
||||||
|
// may be one mutation or a few
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Observer use case
|
||||||
|
|
||||||
|
When `MutationObserver` is needed? Is there a scenario when such thing can be useful?
|
||||||
|
|
||||||
|
Sure, we can track something like `contentEditable` and create "undo/redo" stack, but here's an example where `MutationObserver` is good from architectural standpoint.
|
||||||
|
|
||||||
|
Let's say we're making a website about programming, like this one. Naturally, articles and other materials may contain source code snippets.
|
||||||
|
|
||||||
|
An HTML code snippet looks like this:
|
||||||
|
```html
|
||||||
|
...
|
||||||
|
<pre class="language-javascript"><code>
|
||||||
|
// here's the code
|
||||||
|
let hello = "world";
|
||||||
|
</code></pre>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
There's also a JavaScript highlighting library, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds colored syntax highlighting, similar to what you in examples here, this page.
|
||||||
|
|
||||||
|
Generally, when a page loads, e.g. at the bottom of the page, we can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// highlight all code snippets on the page
|
||||||
|
document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem);
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the `<pre>` snippet looks like this (without line numbers by default):
|
||||||
|
|
||||||
|
```js
|
||||||
|
// here's the code
|
||||||
|
let hello = "world";
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything's simple so far, right? There are `<pre>` code snippets in HTML, we highlight them.
|
||||||
|
|
||||||
|
Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that [later in the tutorial](info:fetch-basics). For now it only matters that we fetch an HTML article from a webserver and display it on demand:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let article = /* fetch new content from server */
|
||||||
|
articleElem.innerHTML = article;
|
||||||
|
```
|
||||||
|
|
||||||
|
The new `article` HTML may contain code snippets. We need to call `Prism.highlightElem` on them, otherwise they won't get highlighted.
|
||||||
|
|
||||||
|
**Who's responsibility is to call `Prism.highlightElem` for a dynamically loaded article?**
|
||||||
|
|
||||||
|
We could append that call to the code that loads an article, like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let article = /* fetch new content from server */
|
||||||
|
articleElem.innerHTML = article;
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
|
||||||
|
snippets.forEach(Prism.highlightElem);
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
...But imagine, we have many places where we load contents with code: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? Then we need to be careful, not to forget about it.
|
||||||
|
|
||||||
|
And what if we load the content into a third-party engine? E.g. we have a forum written by someone else, that loads contents dynamically, and we'd like to add syntax highlighting to it. No one likes to patch third-party scripts.
|
||||||
|
|
||||||
|
Luckily, there's another option.
|
||||||
|
|
||||||
|
We can use `MutationObserver` to automatically detect code snippets inserted in the page and highlight them.
|
||||||
|
|
||||||
|
So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.
|
||||||
|
|
||||||
|
## Dynamic highlight demo
|
||||||
|
|
||||||
|
Here's the working example.
|
||||||
|
|
||||||
|
If you run this code, it starts observing the element below and highlighting any code snippets that appear there:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let observer = new MutationObserver(mutations => {
|
||||||
|
|
||||||
|
for(let mutation of mutations) {
|
||||||
|
// examine new nodes
|
||||||
|
|
||||||
|
for(let node of mutation.addedNodes) {
|
||||||
|
// skip newly added text nodes
|
||||||
|
if (!(node instanceof HTMLElement)) continue;
|
||||||
|
|
||||||
|
// check the inserted element for being a code snippet
|
||||||
|
if (node.matches('pre[class*="language-"]')) {
|
||||||
|
Prism.highlightElement(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// search its subtree for code snippets
|
||||||
|
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
|
||||||
|
Prism.highlightElement(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
let demoElem = document.getElementById('highlight-demo');
|
||||||
|
|
||||||
|
observer.observe(demoElem, {childList: true, subtree: true});
|
||||||
|
```
|
||||||
|
|
||||||
|
<p id="highlight-demo" style="border: 1px solid #ddd">Demo element with <code>id="highlight-demo"</code>, obverved by the example above.</p>
|
||||||
|
|
||||||
|
The code below populates `innerHTML`. If you've run the code above, snippets will get highlighted:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let demoElem = document.getElementById('highlight-demo');
|
||||||
|
|
||||||
|
// dynamically insert content with code snippets
|
||||||
|
demoElem.innerHTML = `A code snippet is below:
|
||||||
|
<pre class="language-javascript"><code> let hello = "world!"; </code></pre>
|
||||||
|
<div>Another one:</div>
|
||||||
|
<div>
|
||||||
|
<pre class="language-css"><code>.class { margin: 5px; } </code></pre>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we have `MutationObserver` that can track all highlighting in observed elements or the whole `document`. We can add/remove code snippets in HTML without thinking about it.
|
||||||
|
|
||||||
|
|
||||||
|
## Garbage collection
|
||||||
|
|
||||||
|
Observers use weak references to nodes internally. That is: if a node is removed from DOM, and becomes unreachable, then it becomes garbage collected, an observer doesn't prevent that.
|
||||||
|
|
||||||
|
Still, we can release observers any time:
|
||||||
|
|
||||||
|
- `observer.disconnect()` -- stops the observation.
|
||||||
|
|
||||||
|
Additionally:
|
||||||
|
|
||||||
|
- `mutationRecords = observer.takeRecords()` -- gets a list of unprocessed mutation records, those that happened, but the callback did not handle them.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// we're going to disconnect the observer
|
||||||
|
// it might have not yet handled some mutations
|
||||||
|
let mutationRecords = observer.takeRecords();
|
||||||
|
// process mutationRecords
|
||||||
|
|
||||||
|
// now all handled, disconnect
|
||||||
|
observer.disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content.
|
||||||
|
|
||||||
|
We can use it to track changes introduced by other parts of our own or 3rd-party code.
|
||||||
|
|
||||||
|
For example, to post-process dynamically inserted content, as demo `innerHTML`, like highlighting in the example above.
|
4
10-misc/index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
|
||||||
|
Not yet categorized articles.
|
|
@ -335,6 +335,74 @@ An example of copying the message:
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## DocumentFragment [#document-fragment]
|
||||||
|
|
||||||
|
`DocumentFragment` is a special DOM node that serves as a wrapper to pass around groups of nodes.
|
||||||
|
|
||||||
|
We can append other nodes to it, but when we insert it somewhere, then it "disappears", leaving its content inserted instead.
|
||||||
|
|
||||||
|
For example, `getListContent` below generates a fragment with `<li>` items, that are later inserted into `<ul>`:
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<ul id="ul"></ul>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getListContent() {
|
||||||
|
let fragment = new DocumentFragment();
|
||||||
|
|
||||||
|
for(let i=1; i<=3; i++) {
|
||||||
|
let li = document.createElement('li');
|
||||||
|
li.append(i);
|
||||||
|
fragment.append(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
ul.append(getListContent()); // (*)
|
||||||
|
*/!*
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note, at the last line `(*)` we append `DocumentFragment`, but it "blends in", so the resulting structure will be:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul>
|
||||||
|
<li>1</li>
|
||||||
|
<li>2</li>
|
||||||
|
<li>3</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
`DocumentFragment` is rarely used explicitly. Why append to a special kind of node, if we can return an array of nodes instead? Rewritten example:
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<ul id="ul"></ul>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getListContent() {
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
for(let i=1; i<=3; i++) {
|
||||||
|
let li = document.createElement('li');
|
||||||
|
li.append(i);
|
||||||
|
result.push(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
ul.append(...getListContent()); // append + "..." operator = friends!
|
||||||
|
*/!*
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
We mention `DocumentFragment` mainly because there are some concepts on top of it, like [template](info:template-element) element, that we'll cover much later.
|
||||||
|
|
||||||
|
|
||||||
## Removal methods
|
## Removal methods
|
||||||
|
|
||||||
To remove nodes, there are the following methods:
|
To remove nodes, there are the following methods:
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
# Resource loading: onload and onerror
|
|
||||||
|
|
||||||
The browser allows to track the loading of external resources -- scripts, iframes, pictures and so on.
|
|
||||||
|
|
||||||
There are two events for it:
|
|
||||||
|
|
||||||
- `onload` -- successful load,
|
|
||||||
- `onerror` -- an error occurred.
|
|
||||||
|
|
||||||
## Loading a script
|
|
||||||
|
|
||||||
Let's say we need to call a function that resides in an external script.
|
|
||||||
|
|
||||||
We can load it dynamically, like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let script = document.createElement('script');
|
|
||||||
script.src = "my.js";
|
|
||||||
|
|
||||||
document.head.append(script);
|
|
||||||
```
|
|
||||||
|
|
||||||
...But how to run the function that is declared inside that script? We need to wait until the script loads, and only then we can call it.
|
|
||||||
|
|
||||||
### script.onload
|
|
||||||
|
|
||||||
The main helper is the `load` event. It triggers after the script was loaded and executed.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run untrusted
|
|
||||||
let script = document.createElement('script');
|
|
||||||
|
|
||||||
// can load any script, from any domain
|
|
||||||
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
|
|
||||||
document.head.append(script);
|
|
||||||
|
|
||||||
*!*
|
|
||||||
script.onload = function() {
|
|
||||||
// the script creates a helper function "_"
|
|
||||||
alert(_); // the function is available
|
|
||||||
};
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
So in `onload` we can use script variables, run functions etc.
|
|
||||||
|
|
||||||
...And what if the loading failed? For instance, there's no such script (error 404) or the server or the server is down (unavailable).
|
|
||||||
|
|
||||||
### script.onerror
|
|
||||||
|
|
||||||
Errors that occur during the loading (but not execution) of the script can be tracked on `error` event.
|
|
||||||
|
|
||||||
For instance, let's request a script that doesn't exist:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let script = document.createElement('script');
|
|
||||||
script.src = "https://example.com/404.js"; // no such script
|
|
||||||
document.head.append(script);
|
|
||||||
|
|
||||||
*!*
|
|
||||||
script.onerror = function() {
|
|
||||||
alert("Error loading " + this.src); // Error loading https://example.com/404.js
|
|
||||||
};
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that we can't get error details here. We don't know was it error 404 or 500 or something else. Just that the loading failed.
|
|
||||||
|
|
||||||
## Other resources
|
|
||||||
|
|
||||||
The `load` and `error` events also work for other resources. There may be minor differences though.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
`<img>`, `<link>` (external stylesheets)
|
|
||||||
: Both `load` and `error` events work as expected.
|
|
||||||
|
|
||||||
`<iframe>`
|
|
||||||
: Only `load` event when the iframe loading finished. It triggers both for successful load and in case of an error. That's for historical reasons.
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Pictures `<img>`, external styles, scripts and other resources provide `load` and `error` events to track their loading:
|
|
||||||
|
|
||||||
- `load` triggers on a successful load,
|
|
||||||
- `error` triggers on a failed load.
|
|
||||||
|
|
||||||
The only exception is `<iframe>`: for historical reasons it always triggers `load`, for any load completion, even if the page is not found.
|
|
||||||
|
|
||||||
The `readystatechange` event also works for resources, but is rarely used, because `load/error` events are simpler.
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Events in details
|
# UI Events
|
||||||
|
|
||||||
Here we cover most important events and details of working with them.
|
Here we cover most important user interface events and how to work with them.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Page lifecycle: DOMContentLoaded, load, beforeunload, unload
|
# Page: DOMContentLoaded, load, beforeunload, unload
|
||||||
|
|
||||||
The lifecycle of an HTML page has three important events:
|
The lifecycle of an HTML page has three important events:
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ Each event may be useful:
|
||||||
|
|
||||||
- `DOMContentLoaded` event -- DOM is ready, so the handler can lookup DOM nodes, initialize the interface.
|
- `DOMContentLoaded` event -- DOM is ready, so the handler can lookup DOM nodes, initialize the interface.
|
||||||
- `load` event -- additional resources are loaded, we can get image sizes (if not specified in HTML/CSS) etc.
|
- `load` event -- additional resources are loaded, we can get image sizes (if not specified in HTML/CSS) etc.
|
||||||
- `beforeunload/unload` event -- the user is leaving: we can check if the user saved the changes and ask them whether they really want to leave.
|
- `beforeunload` event -- the user is leaving: we can check if the user saved the changes and ask them whether they really want to leave.
|
||||||
|
- `unload` -- the user almost left, but we still can initiate some operations, such as sending out statistics.
|
||||||
|
|
||||||
Let's explore the details of these events.
|
Let's explore the details of these events.
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ We must use `addEventListener` to catch it:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
document.addEventListener("DOMContentLoaded", ready);
|
document.addEventListener("DOMContentLoaded", ready);
|
||||||
|
// not "document.onDOMContentLoaded = ..."
|
||||||
```
|
```
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
@ -43,40 +45,46 @@ For instance:
|
||||||
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
|
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example the `DOMContentLoaded` handler runs when the document is loaded and does not wait for the image to load. So `alert` shows zero sizes.
|
In the example the `DOMContentLoaded` handler runs when the document is loaded, so it can see all the elements, including `<img>` below.
|
||||||
|
|
||||||
At the first sight `DOMContentLoaded` event is very simple. The DOM tree is ready -- here's the event. But there are few peculiarities.
|
But it doesn't wait for the image to load. So `alert` shows zero sizes.
|
||||||
|
|
||||||
|
At the first sight `DOMContentLoaded` event is very simple. The DOM tree is ready -- here's the event. There are few peculiarities though.
|
||||||
|
|
||||||
### DOMContentLoaded and scripts
|
### DOMContentLoaded and scripts
|
||||||
|
|
||||||
When the browser initially loads HTML and comes across a `<script>...</script>` in the text, it can't continue building DOM. It must execute the script right now. So `DOMContentLoaded` may only happen after all such scripts are executed.
|
When the browser processes an HTML-document and comes across a `<script>` tag, it needs to execute before continuing building the DOM. That's a precaution, as scripts may want to modify DOM, and even `document.write` into it, so `DOMContentLoaded` has to wait.
|
||||||
|
|
||||||
External scripts (with `src`) also put DOM building to pause while the script is loading and executing. So `DOMContentLoaded` waits for external scripts as well.
|
So DOMContentLoaded definitely happens after such scripts:
|
||||||
|
|
||||||
The only exception are external scripts with `async` and `defer` attributes. They tell the browser to continue processing without waiting for the scripts. This lets the user see the page before scripts finish loading, which is good for performance.
|
```html run
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
console.log("DOM ready!");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
### Scripts with `async` and `defer`
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
|
||||||
|
|
||||||
Attributes `async` and `defer` work only for external scripts. They are ignored if there's no `src`.
|
<script>
|
||||||
|
alert("Library loaded, inline script executed");
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
Both of them tell the browser that it may go on working with the page, and load the script "in background", then run the script when it loads. So the script doesn't block DOM building and page rendering.
|
In the example above, we first see "Library loaded...", and then "DOM ready!" (all scripts are executed).
|
||||||
|
|
||||||
There are two differences between them.
|
```warn header="Scripts with `async`, `defer` or `type=\"module\"` don't block DOMContentLoaded"
|
||||||
|
|
||||||
| | `async` | `defer` |
|
Script attributes `async` and `defer`, that we'll cover [a bit later](info:script-async-defer), don't block DOMContentLoaded. [Javascript modules](info:modules) behave like `defer`, they don't block it too.
|
||||||
|---------|---------|---------|
|
|
||||||
| Order | Scripts with `async` execute *in the load-first order*. Their document order doesn't matter -- which loads first runs first. | Scripts with `defer` always execute *in the document order* (as they go in the document). |
|
|
||||||
| `DOMContentLoaded` | Scripts with `async` 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. | Scripts with `defer` execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |
|
|
||||||
|
|
||||||
So `async` is used for independent scripts, like counters or ads, that don't need to access page content. And their relative execution order does not matter.
|
So here we're talking about "regular" scripts, like `<script>...</script>`, or `<script src="..."></script>`.
|
||||||
|
```
|
||||||
While `defer` is used for scripts that need DOM and/or their relative execution order is important.
|
|
||||||
|
|
||||||
### DOMContentLoaded and styles
|
### DOMContentLoaded and styles
|
||||||
|
|
||||||
External style sheets don't affect DOM, and so `DOMContentLoaded` does not wait for them.
|
External style sheets don't affect DOM, so `DOMContentLoaded` does not wait for them.
|
||||||
|
|
||||||
But there's a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute:
|
But there's a pitfall. Isf we have a script after the style, then that script must wait until the stylesheet loads:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link type="text/css" rel="stylesheet" href="style.css">
|
<link type="text/css" rel="stylesheet" href="style.css">
|
||||||
|
@ -98,7 +106,6 @@ For instance, if the page has a form with login and password, and the browser re
|
||||||
|
|
||||||
So if `DOMContentLoaded` is postponed by long-loading scripts, then autofill also awaits. You probably saw that on some sites (if you use browser autofill) -- the login/password fields don't get autofilled immediately, but there's a delay till the page fully loads. That's actually the delay until the `DOMContentLoaded` event.
|
So if `DOMContentLoaded` is postponed by long-loading scripts, then autofill also awaits. You probably saw that on some sites (if you use browser autofill) -- the login/password fields don't get autofilled immediately, but there's a delay till the page fully loads. That's actually the delay until the `DOMContentLoaded` event.
|
||||||
|
|
||||||
One of minor benefits in using `async` and `defer` for external scripts -- they don't block `DOMContentLoaded` and don't delay browser autofill.
|
|
||||||
|
|
||||||
## window.onload [#window-onload]
|
## window.onload [#window-onload]
|
||||||
|
|
||||||
|
@ -123,15 +130,15 @@ The example below correctly shows image sizes, because `window.onload` waits for
|
||||||
|
|
||||||
When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows.
|
When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows.
|
||||||
|
|
||||||
This event is a good place to send out analytics.
|
The notable exception is sending analytics.
|
||||||
|
|
||||||
E.g. we have a script that gathers some data about mouse clicks, scrolls, viewed page areas -- the statistics that can help us to see what users want.
|
Let's say we gather data about how the page is used: mouse clicks, scrolls, viewed page areas, and so on.
|
||||||
|
|
||||||
Then `onunload` is the best time to send it out. Regular networking methods such as [fetch](info:fetch-basics) or [XMLHttpRequest](info:xmlhttprequest) don't work well, because we're in the process of leaving the page.
|
Naturally, `unload` event is when the user leaves us, and we'd like to save the data on our server.
|
||||||
|
|
||||||
So, there exist `navigator.sendBeacon(url, data)` method for such needs, described in the specification <https://w3c.github.io/beacon/>.
|
There exists a special `navigator.sendBeacon(url, data)` method for such needs, described in the specification <https://w3c.github.io/beacon/>.
|
||||||
|
|
||||||
It sends the data in background. The transition to another page is not delayed: the browser leaves the page and performs `sendBeacon` in background.
|
It sends the data in background. The transition to another page is not delayed: the browser leaves the page, but still performs `sendBeacon`.
|
||||||
|
|
||||||
Here's how to use it:
|
Here's how to use it:
|
||||||
```js
|
```js
|
||||||
|
@ -148,15 +155,28 @@ window.addEventListener("unload", function() {
|
||||||
|
|
||||||
When the `sendBeacon` request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics).
|
When the `sendBeacon` request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics).
|
||||||
|
|
||||||
|
There's also a `keepalive` flag for doing such "after-page-left" requests in [fetch](info:fetch-basics) method for generic network requests. You can find more information in the chapter <info:fetch-api>.
|
||||||
|
|
||||||
|
|
||||||
If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`.
|
If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`.
|
||||||
|
|
||||||
## window.onbeforeunload [#window.onbeforeunload]
|
## window.onbeforeunload [#window.onbeforeunload]
|
||||||
|
|
||||||
If a visitor initiated navigation away from the page or tries to close the window, the `beforeunload` handler asks for additional confirmation.
|
If a visitor initiated navigation away from the page or tries to close the window, the `beforeunload` handler asks for additional confirmation.
|
||||||
|
|
||||||
It may return a string with the question. Historically browsers used to show it, but as of now only some of them do. That's because certain webmasters abused this event handler by showing misleading and hackish messages.
|
If we cancel the event, the browser may ask the visitor if they are sure.
|
||||||
|
|
||||||
You can try it by running this code and then reloading the page.
|
You can try it by running this code and then reloading the page:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
window.onbeforeunload = function() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used show it as a message, but as the [modern specification](https://html.spec.whatwg.org/#unloading-documents) says, they shouldn't.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
window.onbeforeunload = function() {
|
window.onbeforeunload = function() {
|
||||||
|
@ -164,11 +184,7 @@ window.onbeforeunload = function() {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
```online
|
The behavior was changed, because some webmasters abused this event handler by showing misleading and annoying messages. So right now old browsers still may show it as a message, but aside of that -- there's no way to customize the message shown to the user.
|
||||||
Or you can click on the button in `<iframe>` below to set the handler, and then click the link:
|
|
||||||
|
|
||||||
[iframe src="window-onbeforeunload" border="1" height="80" link edit]
|
|
||||||
```
|
|
||||||
|
|
||||||
## readyState
|
## readyState
|
||||||
|
|
||||||
|
@ -176,9 +192,11 @@ What happens if we set the `DOMContentLoaded` handler after the document is load
|
||||||
|
|
||||||
Naturally, it never runs.
|
Naturally, it never runs.
|
||||||
|
|
||||||
There are cases when we are not sure whether the document is ready or not, for instance an external script with `async` attribute loads and runs asynchronously. Depending on the network, it may load and execute before the document is complete or after that, we can't be sure. So we should be able to know the current state of the document.
|
There are cases when we are not sure whether the document is ready or not. We'd like our function to execute when the DOM is loaded, be it now or later.
|
||||||
|
|
||||||
The `document.readyState` property gives us information about it. There are 3 possible values:
|
The `document.readyState` property tells us about the current loading state.
|
||||||
|
|
||||||
|
There are 3 possible values:
|
||||||
|
|
||||||
- `"loading"` -- the document is loading.
|
- `"loading"` -- the document is loading.
|
||||||
- `"interactive"` -- the document was fully read.
|
- `"interactive"` -- the document was fully read.
|
||||||
|
@ -192,13 +210,15 @@ Like this:
|
||||||
function work() { /*...*/ }
|
function work() { /*...*/ }
|
||||||
|
|
||||||
if (document.readyState == 'loading') {
|
if (document.readyState == 'loading') {
|
||||||
|
// loading yet, wait for the event
|
||||||
document.addEventListener('DOMContentLoaded', work);
|
document.addEventListener('DOMContentLoaded', work);
|
||||||
} else {
|
} else {
|
||||||
|
// DOM is ready!
|
||||||
work();
|
work();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
There's a `readystatechange` event that triggers when the state changes, so we can print all these states like this:
|
There's also `readystatechange` event that triggers when the state changes, so we can print all these states like this:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
// current state
|
// current state
|
||||||
|
@ -208,15 +228,14 @@ console.log(document.readyState);
|
||||||
document.addEventListener('readystatechange', () => console.log(document.readyState));
|
document.addEventListener('readystatechange', () => console.log(document.readyState));
|
||||||
```
|
```
|
||||||
|
|
||||||
The `readystatechange` event is an alternative mechanics of tracking the document loading state, it appeared long ago. Nowadays, it is rarely used, but let's cover it for completeness.
|
The `readystatechange` event is an alternative mechanics of tracking the document loading state, it appeared long ago. Nowadays, it is rarely used.
|
||||||
|
|
||||||
What is the place of `readystatechange` among other events?
|
Let's see the full events flow for the completeness.
|
||||||
|
|
||||||
To see the timing, here's a document with `<iframe>`, `<img>` and handlers that log events:
|
Here's a document with `<iframe>`, `<img>` and handlers that log events:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script>
|
<script>
|
||||||
function log(text) { /* output the time and message */ }
|
|
||||||
log('initial readyState:' + document.readyState);
|
log('initial readyState:' + document.readyState);
|
||||||
|
|
||||||
document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
|
document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
|
||||||
|
@ -244,23 +263,22 @@ The typical output:
|
||||||
6. [4] readyState:complete
|
6. [4] readyState:complete
|
||||||
7. [4] window onload
|
7. [4] window onload
|
||||||
|
|
||||||
The numbers in square brackets denote the approximate time of when it happens. The real time is a bit greater, but events labeled with the same digit happen approximately at the same time (+- a few ms).
|
The numbers in square brackets denote the approximate time of when it happens. Events labeled with the same digit happen approximately at the same time (+- a few ms).
|
||||||
|
|
||||||
- `document.readyState` becomes `interactive` right before `DOMContentLoaded`. These two events actually mean the same.
|
- `document.readyState` becomes `interactive` right before `DOMContentLoaded`. These two things actually mean the same.
|
||||||
- `document.readyState` becomes `complete` when all resources (`iframe` and `img`) are loaded. Here we can see that it happens in about the same time as `img.onload` (`img` is the last resource) and `window.onload`. Switching to `complete` state means the same as `window.onload`. The difference is that `window.onload` always works after all other `load` handlers.
|
- `document.readyState` becomes `complete` when all resources (`iframe` and `img`) are loaded. Here we can see that it happens in about the same time as `img.onload` (`img` is the last resource) and `window.onload`. Switching to `complete` state means the same as `window.onload`. The difference is that `window.onload` always works after all other `load` handlers.
|
||||||
|
|
||||||
|
|
||||||
## Lifecycle events summary
|
## Summary
|
||||||
|
|
||||||
Page lifecycle events:
|
Page load events:
|
||||||
|
|
||||||
- `DOMContentLoaded` event triggers on `document` when DOM is ready. We can apply JavaScript to elements at this stage.
|
- `DOMContentLoaded` event triggers on `document` when DOM is ready. We can apply JavaScript to elements at this stage.
|
||||||
- All inline scripts and scripts with `defer` are already executed.
|
- Script such as `<script>...</script>` or `<script src="..."></script>` block DOMContentLoaded, the browser waits for them to execute.
|
||||||
- Async scripts may execute both before and after the event, depends on when they load.
|
|
||||||
- Images and other resources may also still continue loading.
|
- Images and other resources may also still continue loading.
|
||||||
- `load` event on `window` triggers when the page and all resources are loaded. We rarely use it, because there's usually no need to wait for so long.
|
- `load` event on `window` triggers when the page and all resources are loaded. We rarely use it, because there's usually no need to wait for so long.
|
||||||
- `beforeunload` event on `window` triggers when the user wants to leave the page. If it returns a string, the browser shows a question whether the user really wants to leave or not.
|
- `beforeunload` event on `window` triggers when the user wants to leave the page. If we cancel the event, browser asks whether the user really wants to leave (e.g we have unsaved changes).
|
||||||
- `unload` event on `window` triggers when the user is finally leaving, in the handler we can only do simple things that do not involve delays or asking a user. Because of that limitation, it's rarely used.
|
- `unload` event on `window` triggers when the user is finally leaving, in the handler we can only do simple things that do not involve delays or asking a user. Because of that limitation, it's rarely used. We can send out a network request with `navigator.sendBeacon`.
|
||||||
- `document.readyState` is the current state of the document, changes can be tracked in the `readystatechange` event:
|
- `document.readyState` is the current state of the document, changes can be tracked in the `readystatechange` event:
|
||||||
- `loading` -- the document is loading.
|
- `loading` -- the document is loading.
|
||||||
- `interactive` -- the document is parsed, happens at about the same time as `DOMContentLoaded`, but before it.
|
- `interactive` -- the document is parsed, happens at about the same time as `DOMContentLoaded`, but before it.
|
197
2-ui/5-loading/02-script-async-defer/article.md
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
|
||||||
|
# Scripts: async, defer
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
1. Scripts can't see DOM elements below them, so can't add handlers etc.
|
||||||
|
2. 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:
|
||||||
|
|
||||||
|
```html run height=100
|
||||||
|
<p>...content before script...</p>
|
||||||
|
|
||||||
|
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
|
||||||
|
|
||||||
|
<!-- This isn't visible until the script loads -->
|
||||||
|
<p>...content after script...</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<body>
|
||||||
|
...all content is above the script...
|
||||||
|
|
||||||
|
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
But this solution is far from perfect. For example, the browser actually 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 far-from-perfect mobile connectivity.
|
||||||
|
|
||||||
|
Luckily, there are two `<script>` attributes that solve the problem for us: `defer` and `async`
|
||||||
|
|
||||||
|
## defer
|
||||||
|
|
||||||
|
The `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 `defer`:
|
||||||
|
|
||||||
|
```html run height=100
|
||||||
|
<p>...content before script...</p>
|
||||||
|
|
||||||
|
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
|
||||||
|
|
||||||
|
<!-- visible immediately -->
|
||||||
|
<p>...content after script...</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Scripts with `defer` never block the page.
|
||||||
|
- Scripts with `defer` always execute when the DOM is ready, but before `DOMContentLoaded` event.
|
||||||
|
|
||||||
|
The following example demonstrates that:
|
||||||
|
|
||||||
|
```html run height=100
|
||||||
|
<p>...content before scripts...</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!")); // (2)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
|
||||||
|
|
||||||
|
<p>...content after scripts...</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The page content shows up immediately.
|
||||||
|
2. `DOMContentLoaded` waits 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.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
|
||||||
|
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart header="The small script downloads first, runs second"
|
||||||
|
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 requres scripts to execute in the document order, so it waits for `long.js` to execute.
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart header="The `defer` attribute is only for external scripts"
|
||||||
|
The `defer` attribute is ignored if the script has no `src`.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## async
|
||||||
|
|
||||||
|
The `async` attribute means that a script is completely independant:
|
||||||
|
|
||||||
|
- The page doesn't wait for async scripts, the contents is processed and displayed.
|
||||||
|
- `DOMContentLoaded` and async scripts don't wait each other:
|
||||||
|
- `DOMContentLoaded` may 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 `async` scripts, and `async` scripts don't wait for them.
|
||||||
|
|
||||||
|
|
||||||
|
So, if we have several `async` scripts, they may execute in any order. Whatever loads first -- runs first:
|
||||||
|
|
||||||
|
```html run height=100
|
||||||
|
<p>...content before scripts...</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
|
||||||
|
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
|
||||||
|
|
||||||
|
<p>...content after scripts...</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The page content shows up immediately: `async` doesn't block it.
|
||||||
|
2. `DOMContentLoaded` may happen both before and after `async`, no guarantees here.
|
||||||
|
3. Async scripts don't wait for each other. A smaller script `small.js` goes 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 independant third-party script into the page: counters, ads and so on.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script async src="https://google-analytics.com/analytics.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Dynamic scripts
|
||||||
|
|
||||||
|
We can also create a script dynamically using Javascript:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = "/article/script-async-defer/long.js";
|
||||||
|
document.body.append(script); // (*)
|
||||||
|
```
|
||||||
|
|
||||||
|
The script starts loading as soon as it's appended to the document `(*)`.
|
||||||
|
|
||||||
|
**Dynamic scripts behave as "async" by default.**
|
||||||
|
|
||||||
|
That is:
|
||||||
|
- 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 by explicitly setting `async` to `false`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = "/article/script-async-defer/long.js";
|
||||||
|
|
||||||
|
*!*
|
||||||
|
script.async = false;
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
document.body.append(script);
|
||||||
|
```
|
||||||
|
|
||||||
|
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":
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function loadScript(src) {
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = src;
|
||||||
|
script.async = false;
|
||||||
|
document.body.append(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
// long.js runs first because of async=false
|
||||||
|
loadScript("/article/script-async-defer/long.js");
|
||||||
|
loadScript("/article/script-async-defer/small.js");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Both `async` and `defer` have one common thing: they don'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:
|
||||||
|
|
||||||
|
| | Order | `DOMContentLoaded` |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `async` | *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. |
|
||||||
|
| `defer` | *Document order* (as they go in the document). | Execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |
|
||||||
|
|
||||||
|
```warn header="Page without scripts should be usable"
|
||||||
|
Please note that if you're using `defer`, then the page is visible before the script loads and enables all the graphical components.
|
||||||
|
|
||||||
|
So, buttons should be disabled by CSS or by other means, to let the user
|
||||||
|
|
||||||
|
In practice, `defer` is used for scripts that need DOM and/or their relative execution order is important.
|
||||||
|
|
||||||
|
|
||||||
|
So `async` is used for independent scripts, like counters or ads, that don't need to access page content. And their relative execution order does not matter.
|
32
2-ui/5-loading/02-script-async-defer/long.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
|
||||||
|
|
||||||
|
alert("Long script loaded");
|
3
2-ui/5-loading/02-script-async-defer/small.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// ...small js...
|
||||||
|
|
||||||
|
alert("Small script loaded");
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
function setHandler() {
|
||||||
|
window.onbeforeunload = function() {
|
||||||
|
return "There are unsaved changes. Leave now?";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick="setHandler()">Set window.onbeforeunload</button>
|
||||||
|
|
||||||
|
<a href="http://example.com">Leave for EXAMPLE.COM</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
206
2-ui/5-loading/03-onload-onerror/article.md
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
# Resource loading: onload and onerror
|
||||||
|
|
||||||
|
The browser allows to track the loading of external resources -- scripts, iframes, pictures and so on.
|
||||||
|
|
||||||
|
There are two events for it:
|
||||||
|
|
||||||
|
- `onload` -- successful load,
|
||||||
|
- `onerror` -- an error occurred.
|
||||||
|
|
||||||
|
## Loading a script
|
||||||
|
|
||||||
|
Let's say we need to load a third-party script and call a function that resides there.
|
||||||
|
|
||||||
|
We can load it dynamically, like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = "my.js";
|
||||||
|
|
||||||
|
document.head.append(script);
|
||||||
|
```
|
||||||
|
|
||||||
|
...But how to run the function that is declared inside that script? We need to wait until the script loads, and only then we can call it.
|
||||||
|
|
||||||
|
```smart
|
||||||
|
For our own scripts we could use [Javascript modules](info:modules) here, but they are not widely adopted by third-party libraries.
|
||||||
|
```
|
||||||
|
|
||||||
|
### script.onload
|
||||||
|
|
||||||
|
The main helper is the `load` event. It triggers after the script was loaded and executed.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run untrusted
|
||||||
|
let script = document.createElement('script');
|
||||||
|
|
||||||
|
// can load any script, from any domain
|
||||||
|
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
|
||||||
|
document.head.append(script);
|
||||||
|
|
||||||
|
*!*
|
||||||
|
script.onload = function() {
|
||||||
|
// the script creates a helper function "_"
|
||||||
|
alert(_); // the function is available
|
||||||
|
};
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
So in `onload` we can use script variables, run functions etc.
|
||||||
|
|
||||||
|
...And what if the loading failed? For instance, there's no such script (error 404) or the server or the server is down (unavailable).
|
||||||
|
|
||||||
|
### script.onerror
|
||||||
|
|
||||||
|
Errors that occur during the loading of the script can be tracked on `error` event.
|
||||||
|
|
||||||
|
For instance, let's request a script that doesn't exist:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = "https://example.com/404.js"; // no such script
|
||||||
|
document.head.append(script);
|
||||||
|
|
||||||
|
*!*
|
||||||
|
script.onerror = function() {
|
||||||
|
alert("Error loading " + this.src); // Error loading https://example.com/404.js
|
||||||
|
};
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that we can't get HTTP error details here. We don't know was it error 404 or 500 or something else. Just that the loading failed.
|
||||||
|
|
||||||
|
```warn
|
||||||
|
Events `onload`/`onerror` track only the loading itself.
|
||||||
|
|
||||||
|
Errors during script processing and execution are out of the scope of these events. To track script errors, one can use `window.onerror` global handler.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other resources
|
||||||
|
|
||||||
|
The `load` and `error` events also work for other resources, basically for any resource that has an external `src`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let img = document.createElement('img');
|
||||||
|
img.src = "https://js.cx/clipart/train.gif"; // (*)
|
||||||
|
|
||||||
|
img.onload = function() {
|
||||||
|
alert(`Image loaded, size ${img.width}x${img.height}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = function() {
|
||||||
|
alert("Error occured while loading image");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some notes though:
|
||||||
|
|
||||||
|
- Most resources start loading when they are added to the document. But `<img>` is an exception. It starts loading when it gets an src `(*)`.
|
||||||
|
- For `<iframe>`, the `iframe.onload` event triggers when the iframe loading finished, both for successful load and in case of an error.
|
||||||
|
|
||||||
|
That's for historical reasons.
|
||||||
|
|
||||||
|
## Crossorigin policy
|
||||||
|
|
||||||
|
There's a rule: scripts from one site can't access contents of the other site. So, e.g. a script at `https://facebook.com` can't read the user's mailbox at `https://gmail.com`.
|
||||||
|
|
||||||
|
Or, to be more precise, one origin (domain/port/protocol triplet) can't access the content from another one. So even if we have a subdomain, or just another port, these are different origins, no access to each other.
|
||||||
|
|
||||||
|
This rule also affects resources from other domains.
|
||||||
|
|
||||||
|
If we're using a script from another domain, and there's an error in it, we can't get error details.
|
||||||
|
|
||||||
|
For example, let's take a script with a single (bad) function call:
|
||||||
|
```js
|
||||||
|
// 📁 error.js
|
||||||
|
noSuchFunction();
|
||||||
|
```
|
||||||
|
|
||||||
|
Now load it from our domain:
|
||||||
|
|
||||||
|
```html run height=0
|
||||||
|
<script>
|
||||||
|
window.onerror = function(message, url, line, col, errorObj) {
|
||||||
|
alert(`${message}\n${url}, ${line}:${col}`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="/article/onload-onerror/crossorigin/error.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
We can see a good error report, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Uncaught ReferenceError: noSuchFunction is not defined
|
||||||
|
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
|
||||||
|
```
|
||||||
|
|
||||||
|
Now let's load the same script from another domain:
|
||||||
|
|
||||||
|
```html run height=0
|
||||||
|
<script>
|
||||||
|
window.onerror = function(message, url, line, col, errorObj) {
|
||||||
|
alert(`${message}\n${url}, ${line}:${col}`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
The report is different, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Script error.
|
||||||
|
, 0:0
|
||||||
|
```
|
||||||
|
|
||||||
|
Details may vary depeding on the browser, but the idea is same: any information about the internals of a script is hidden. Exactly because it's from another domain.
|
||||||
|
|
||||||
|
Why do we need the details?
|
||||||
|
|
||||||
|
There are many services (and we can build our own) that listen to `window.onerror`, save errors at the server and provide an interface to access and analyze them. That's great, as we can see real errors, triggered by our users. But we can't see any error information for scripts from other domains.
|
||||||
|
|
||||||
|
Similar cross-origin policy (CORS) is enforced for other types of resources as well.
|
||||||
|
|
||||||
|
**To allow cross-origin access, we need `crossorigin` attribute, plus the remote server must provide special headers.**
|
||||||
|
|
||||||
|
There are three levels of cross-origin access:
|
||||||
|
|
||||||
|
1. **No `crossorigin` attribute** -- access prohibited.
|
||||||
|
2. **`crossorigin="anonymous"`** -- access allowed if the server responds with the header `Access-Control-Allow-Origin` with `*` or our origin. Browser does not send authorization information and cookies to remote server.
|
||||||
|
3. **`crossorigin="use-credentials"`** -- access allowed if the server sends back the header `Access-Control-Allow-Origin` with our origin and `Access-Control-Allow-Credentials: true`. Browser sends authorization information and cookies to remote server.
|
||||||
|
|
||||||
|
```smart
|
||||||
|
You can read more about cross-origin access in the chapter <info:fetch-crossorigin>. It describes `fetch` method for network requests, but the policy is exactly the same.
|
||||||
|
|
||||||
|
Such thing as "cookies" is out of our current scope, but you can read about them in the chapter <info:cookie>.
|
||||||
|
```
|
||||||
|
|
||||||
|
In our case, we didn't have any crossorigin attribute. So the cross-origin access was prohibited. Let's add it.
|
||||||
|
|
||||||
|
We can choose between `"anonymous"` (no cookies sent, one server-side header needed) and `"use-credentials"` (sends cookies too, two server-side headers needed).
|
||||||
|
|
||||||
|
If we don't care about cookies, then `"anonymous"` is a way to go:
|
||||||
|
|
||||||
|
```html run height=0
|
||||||
|
<script>
|
||||||
|
window.onerror = function(message, url, line, col, errorObj) {
|
||||||
|
alert(`${message}\n${url}, ${line}:${col}`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script *!*crossorigin="anonymous"*/!* src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, assuming that the server provides `Access-Control-Allow-Origin` header, everything's fine. We have the full error report.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Images `<img>`, external styles, scripts and other resources provide `load` and `error` events to track their loading:
|
||||||
|
|
||||||
|
- `load` triggers on a successful load,
|
||||||
|
- `error` triggers on a failed load.
|
||||||
|
|
||||||
|
The only exception is `<iframe>`: for historical reasons it always triggers `load`, for any load completion, even if the page is not found.
|
||||||
|
|
||||||
|
The `readystatechange` event also works for resources, but is rarely used, because `load/error` events are simpler.
|
|
@ -0,0 +1 @@
|
||||||
|
noSuchFunction();
|
2
2-ui/5-loading/index.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
# Document and resource loading
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
@ -181,11 +181,11 @@ They exist for historical reasons, to get either a string or XML document. Nowad
|
||||||
All states, as in [the specification](https://xhr.spec.whatwg.org/#states):
|
All states, as in [the specification](https://xhr.spec.whatwg.org/#states):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const unsigned short UNSENT = 0; // initial state
|
UNSENT = 0; // initial state
|
||||||
const unsigned short OPENED = 1; // open called
|
OPENED = 1; // open called
|
||||||
const unsigned short HEADERS_RECEIVED = 2; // response headers received
|
HEADERS_RECEIVED = 2; // response headers received
|
||||||
const unsigned short LOADING = 3; // response is loading (a data packed is received)
|
LOADING = 3; // response is loading (a data packed is received)
|
||||||
const unsigned short DONE = 4; // request complete
|
DONE = 4; // request complete
|
||||||
```
|
```
|
||||||
|
|
||||||
An `XMLHttpRequest` object travels them in the order `0` -> `1` -> `2` -> `3` -> ... -> `3` -> `4`. State `3` repeats every time a data packet is received over the network.
|
An `XMLHttpRequest` object travels them in the order `0` -> `1` -> `2` -> `3` -> ... -> `3` -> `4`. State `3` repeats every time a data packet is received over the network.
|