diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index ff6c1527..0aab0689 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -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: -``` +```js (function() { 'use strict'; diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md index f5684a6d..9f1ade70 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md @@ -1,5 +1,5 @@ ```js run no-beautify -function sortByName(arr) { +function sortByAge(arr) { 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 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] alert(arr[0].name); // John +alert(arr[1].name); // Mary alert(arr[2].name); // Pete ``` diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md index fae6bcbe..9a215c9f 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md @@ -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: @@ -13,11 +13,12 @@ let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; -let arr = [ john, pete, mary ]; +let arr = [ pete, john, mary ]; -sortByName(arr); +sortByAge(arr); // now: [john, mary, pete] alert(arr[0].name); // John +alert(arr[1].name); // Mary alert(arr[2].name); // Pete ``` diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md index 0183a479..735a446f 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md @@ -5,7 +5,7 @@ let i = 0; let start = Date.now(); -let timer = setInterval(count, 0); +let timer = setInterval(count); function count() { @@ -20,4 +20,3 @@ function count() { } ``` - diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md index 3c788170..c3455c2a 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md @@ -18,7 +18,7 @@ function count() { if (i == 1000000000) { alert("Done in " + (Date.now() - start) + 'ms'); } else { - setTimeout(count, 0); + setTimeout(count); } // a piece of heavy job diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index dfc08b77..8484d373 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -15,7 +15,7 @@ These methods are not a part of JavaScript specification. But most environments The syntax: ```js -let timerId = setTimeout(func|code, delay[, arg1, arg2...]) +let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) ``` 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. `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`... : 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`: ```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. @@ -238,7 +238,7 @@ There's a side-effect. A function references the outer lexical environment, so, ## 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. @@ -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": ```js run -setTimeout(() => alert("World"), 0); +setTimeout(() => alert("World")); alert("Hello"); ``` @@ -260,7 +260,7 @@ There's a trick to split CPU-hungry tasks using `setTimeout`. For instance, a syntax-highlighting script (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a big text that takes a lot. It may even cause the browser to "hang", which is unacceptable. -So we can split the long text into pieces. First 100 lines, then plan another 100 lines using `setTimeout(...,0)`, and so on. +So we can split the long text into pieces. First 100 lines, then plan another 100 lines using `setTimeout(..., 0)`, and so on. For clarity, let's take a simpler example for consideration. We have a function to count from `1` to `1000000000`. @@ -303,7 +303,7 @@ function count() { if (i == 1e9) { alert("Done in " + (Date.now() - start) + 'ms'); } 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 if (i < 1e9 - 1e6) { - setTimeout(count, 0); // schedule the new call + setTimeout(count); // schedule the new call } do { @@ -371,8 +371,8 @@ setTimeout(function run() { times.push(Date.now() - start); // remember delay from the previous call if (start + 100 < Date.now()) alert(times); // show the delays after 100ms - else setTimeout(run, 0); // else re-schedule -}, 0); + else setTimeout(run); // else re-schedule +}); // 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 @@ -430,7 +430,7 @@ And if we use `setTimeout` to split it into pieces then changes are applied in-b } while (i % 1e3 != 0); if (i < 1e9) { - setTimeout(count, 0); + setTimeout(count); } } diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index ad83d425..19601e16 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -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 *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: - external module scripts ` +``` + +If we change the text inside `me`, we'll get a single mutation: + +```js +mutationRecords = [{ + type: "characterData", + oldValue: "me", + target: , + // other properties empty +}]; +``` + +If we select and remove the `me` altogether, we'll get multiple mutations: + +```js +mutationRecords = [{ + type: "childList", + target: , + removedNodes: [], + nextSibling: , + previousSibling: + // other properties empty +}, { + type: "characterData" + target: + // ...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 +... +

+  // here's the code
+  let hello = "world";
+
+... +``` + +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 `
` 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 `
` 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});
+```
+
+

Demo element with id="highlight-demo", obverved by the example above.

+ +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: +
 let hello = "world!"; 
+
Another one:
+
+
.class { margin: 5px; } 
+
+`; +``` + +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. diff --git a/10-misc/index.md b/10-misc/index.md new file mode 100644 index 00000000..65ab3188 --- /dev/null +++ b/10-misc/index.md @@ -0,0 +1,4 @@ + +# Miscellaneous + +Not yet categorized articles. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 22907554..16aacb45 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -136,7 +136,7 @@ Here's a brief list of methods to insert a node into a parent element (`parentEl ``` To insert `newLi` as the first element, we can do it like this: - + ```js list.insertBefore(newLi, list.firstChild); ``` @@ -335,6 +335,74 @@ An example of copying the message: ``` + +## 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 `
  • ` items, that are later inserted into `
      `: + +```html run +
        + + +``` + +Please note, at the last line `(*)` we append `DocumentFragment`, but it "blends in", so the resulting structure will be: + +```html +
          +
        • 1
        • +
        • 2
        • +
        • 3
        • +
        +``` + +`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 +
          + + +``` + +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 To remove nodes, there are the following methods: diff --git a/2-ui/3-event-details/11-onload-onerror/article.md b/2-ui/3-event-details/11-onload-onerror/article.md deleted file mode 100644 index 7792626c..00000000 --- a/2-ui/3-event-details/11-onload-onerror/article.md +++ /dev/null @@ -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: - -``, `` (external stylesheets) -: Both `load` and `error` events work as expected. - -`