Merge pull request #1 from javascript-tutorial/master
Update from original
This commit is contained in:
commit
89d81d369d
26 changed files with 152 additions and 127 deletions
|
@ -3,28 +3,24 @@
|
|||
|
||||
What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`?
|
||||
|
||||
There are special methods in objects that do the conversion.
|
||||
In that case objects are auto-converted to primitives, and then the operation is carried out.
|
||||
|
||||
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to close it.
|
||||
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.
|
||||
|
||||
For objects, there's no to-boolean conversion, because all objects are `true` in a boolean context. So there are only string and numeric conversions.
|
||||
|
||||
The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter <info:date>) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
|
||||
|
||||
As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
|
||||
1. All objects are `true` in a boolean context. There are only numeric and string conversions.
|
||||
2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter <info:date>) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
|
||||
3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
|
||||
|
||||
## ToPrimitive
|
||||
|
||||
When an object is used in the context where a primitive is required, for instance, in an `alert` or mathematical operations, it's converted to a primitive value using the `ToPrimitive` algorithm ([specification](https://tc39.github.io/ecma262/#sec-toprimitive)).
|
||||
We can fine-tune string and numeric conversion, using special object methods.
|
||||
|
||||
That algorithm allows us to customize the conversion using a special object method.
|
||||
|
||||
Depending on the context, the conversion has a so-called "hint".
|
||||
The conversion algorithm is called `ToPrimitive` in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive). It's called with a "hint" that specifies the conversion type.
|
||||
|
||||
There are three variants:
|
||||
|
||||
`"string"`
|
||||
: When an operation expects a string, for object-to-string conversions, like `alert`:
|
||||
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
|
||||
|
||||
```js
|
||||
// output
|
||||
|
@ -35,7 +31,7 @@ There are three variants:
|
|||
```
|
||||
|
||||
`"number"`
|
||||
: When an operation expects a number, for object-to-number conversions, like maths:
|
||||
: For an object-to-number conversion, like when we're doing maths:
|
||||
|
||||
```js
|
||||
// explicit conversion
|
||||
|
@ -52,7 +48,7 @@ There are three variants:
|
|||
`"default"`
|
||||
: Occurs in rare cases when the operator is "not sure" what type to expect.
|
||||
|
||||
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol.
|
||||
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done.
|
||||
|
||||
```js
|
||||
// binary plus
|
||||
|
@ -159,14 +155,21 @@ alert(user + 500); // toString -> John500
|
|||
|
||||
In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions.
|
||||
|
||||
|
||||
## ToPrimitive and ToString/ToNumber
|
||||
## Return types
|
||||
|
||||
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
|
||||
|
||||
There is no control whether `toString()` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint "number".
|
||||
|
||||
**The only mandatory thing: these methods must return a primitive.**
|
||||
The only mandatory thing: these methods must return a primitive, not an object.
|
||||
|
||||
```smart header="Historical notes"
|
||||
For historical reasons, if `toString` or `valueOf` return an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.
|
||||
|
||||
In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error.
|
||||
```
|
||||
|
||||
## Further operations
|
||||
|
||||
An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
|
||||
|
||||
|
@ -208,11 +211,6 @@ For instance:
|
|||
alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber)
|
||||
```
|
||||
|
||||
```smart header="Historical notes"
|
||||
For historical reasons, methods `toString` or `valueOf` *should* return a primitive: if any of them returns an object, then there's no error, but that object is ignored (like if the method didn't exist).
|
||||
|
||||
In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise, there will be an error.
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ alert( fruits );
|
|||
|
||||
## Internals
|
||||
|
||||
An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. Numbers are used as keys.
|
||||
An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. That's essentially the same as `obj[key]`, where `arr` is the object, while numbers are used as keys.
|
||||
|
||||
They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object.
|
||||
|
||||
|
@ -320,7 +320,7 @@ But that's actually a bad idea. There are potential problems with it:
|
|||
|
||||
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The `for..in` loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem.
|
||||
|
||||
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks or seem irrelevant. But still we should be aware of the difference.
|
||||
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks. But still we should be aware of the difference.
|
||||
|
||||
Generally, we shouldn't use `for..in` for arrays.
|
||||
|
||||
|
@ -385,7 +385,7 @@ To evade such surprises, we usually use square brackets, unless we really know w
|
|||
|
||||
## Multidimensional arrays
|
||||
|
||||
Arrays can have items that are also arrays. We can use it for multidimensional arrays, to store matrices:
|
||||
Arrays can have items that are also arrays. We can use it for multidimensional arrays, for example to store matrices:
|
||||
|
||||
```js run
|
||||
let matrix = [
|
||||
|
@ -461,4 +461,3 @@ To loop over the elements of the array:
|
|||
- `for (let i in arr)` -- never use.
|
||||
|
||||
We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter <info:array-methods>.
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ let wrapper = function() {
|
|||
}
|
||||
```
|
||||
|
||||
We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to arguments. The alternative is to use rest parameters object that is a real array.
|
||||
We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array.
|
||||
|
||||
|
||||
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.
|
||||
|
|
|
@ -85,7 +85,7 @@ alert(user.surname); // Cooper
|
|||
Now we have a "virtual" property. It is readable and writable, but in fact does not exist.
|
||||
|
||||
```smart header="Accessor properties are only accessible with get/set"
|
||||
Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data properety any more.
|
||||
Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data property any more.
|
||||
|
||||
- If there's a getter -- we can read `object.prop`, otherwise we can't.
|
||||
- If there's a setter -- we can set `object.prop=...`, otherwise we can't.
|
||||
|
|
|
@ -312,7 +312,6 @@ All other key/value-getting methods, such as `Object.keys`, `Object.values` and
|
|||
They only operate on the object itself. Properties from the prototype are taken into account.
|
||||
```
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
|
||||
|
|
|
@ -49,7 +49,7 @@ Right now they are fully independent.
|
|||
|
||||
But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
|
||||
|
||||
To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`.
|
||||
To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`.
|
||||
|
||||
Here `Rabbit` inherits from `Animal`:
|
||||
|
||||
|
@ -209,7 +209,7 @@ With constructors it gets a little bit tricky.
|
|||
|
||||
Till now, `Rabbit` did not have its own `constructor`.
|
||||
|
||||
According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following `constructor` is generated:
|
||||
According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated:
|
||||
|
||||
```js
|
||||
class Rabbit extends Animal {
|
||||
|
@ -309,15 +309,15 @@ alert(rabbit.earLength); // 10
|
|||
|
||||
Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way.
|
||||
|
||||
First to say, from all that we've learned till now, it's impossible for `super` to work.
|
||||
First to say, from all that we've learned till now, it's impossible for `super` to work at all!
|
||||
|
||||
Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, how to retrieve the `method`? Naturally, we need to take the `method` from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
|
||||
Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object. How JavaScript engine should get the prototype of `this`?
|
||||
|
||||
Maybe we can get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that doesn't work.
|
||||
The task may seem simple, but it isn't. The engine could try to get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`. Unfortunately, that doesn't work.
|
||||
|
||||
Let's try to do it. Without classes, using plain objects for the sake of simplicity.
|
||||
Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.
|
||||
|
||||
Here, `rabbit.eat()` should call `animal.eat()` method of the parent object:
|
||||
In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
|
@ -418,7 +418,7 @@ To provide the solution, JavaScript adds one more special internal property for
|
|||
|
||||
This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. So that's a very important change in the language.
|
||||
|
||||
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility.
|
||||
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility. Regular method calls know nothing about `[[HomeObject]]`, it only matters for `super`.
|
||||
|
||||
Let's see how it works for `super` -- again, using plain objects:
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ That's the correct variant:
|
|||
|
||||
[codetabs src="scopes-working" height="140" current="hello.js"]
|
||||
|
||||
In the browser, independant top-level scope also exists for each `<script type="module">`:
|
||||
In the browser, independent top-level scope also exists for each `<script type="module">`:
|
||||
|
||||
```html run
|
||||
<script type="module">
|
||||
|
@ -263,7 +263,7 @@ When using modules, we should be aware that HTML-document can show up before the
|
|||
|
||||
### Async works on inline scripts
|
||||
|
||||
Async attribute `<script async type="module">` is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independantly of other scripts or the HTML document.
|
||||
Async attribute `<script async type="module">` is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independently of other scripts or the HTML document.
|
||||
|
||||
For example, the script below has `async`, so it doesn't wait for anyone.
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ Well, there are few reasons.
|
|||
export function becomeSilent() { ... }
|
||||
```
|
||||
|
||||
Now if we in fact need only one of them in our project:
|
||||
Now if we only use one of `lib.js` functions in our project:
|
||||
```js
|
||||
// 📁 main.js
|
||||
import {sayHi} from './lib.js';
|
||||
|
@ -218,8 +218,7 @@ export default function(user) { // no function name
|
|||
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
```
|
||||
|
||||
That's fine, because `export default` is only one per file, so `import` always knows what to import.
|
||||
Contrary to that, omitting a name for named imports would be an error:
|
||||
That's fine, because `export default` is only one per file. Contrary to that, omitting a name for named imports would be an error:
|
||||
|
||||
```js
|
||||
export class { // Error! (non-default export needs a name)
|
||||
|
@ -229,9 +228,9 @@ export class { // Error! (non-default export needs a name)
|
|||
|
||||
### "Default" alias
|
||||
|
||||
The "default" word is a kind of "alias" for the default export, for scenarios when we need to reference it somehow.
|
||||
The "default" keyword is used as an "alias" for the default export, for standalone exports and other scenarios when we need to reference it.
|
||||
|
||||
For example, if we already have a function declared, that's how to `export default` it:
|
||||
For example, if we already have a function declared, that's how to `export default` it (separately from the definition):
|
||||
|
||||
```js
|
||||
function sayHi(user) {
|
||||
|
@ -278,7 +277,7 @@ new User('John');
|
|||
|
||||
### Should I use default exports?
|
||||
|
||||
One should be careful about using default exports, because they are somewhat more different to maintain.
|
||||
One should be careful about using default exports, because they are more difficult to maintain.
|
||||
|
||||
Named exports are explicit. They exactly name what they import, so we have that information from them, that's a good thing.
|
||||
|
||||
|
@ -286,12 +285,15 @@ Also, named exports enforce us to use exactly the right name to import:
|
|||
|
||||
```js
|
||||
import {User} from './user.js';
|
||||
// import {MyUser} won't work, the name must be {User}
|
||||
```
|
||||
|
||||
For default exports, we need to create a name on our own:
|
||||
For default exports, we always choose the name when importing:
|
||||
|
||||
```js
|
||||
import MyUser from './user.js'; // could be import Anything..., and it'll work
|
||||
import User from './user.js'; // works
|
||||
import MyUser from './user.js'; // works too
|
||||
// could be import Anything..., and it'll be work
|
||||
```
|
||||
|
||||
So, there's a little bit more freedom that can be abused, so that team members may use different names for the same thing.
|
||||
|
@ -409,16 +411,18 @@ Import:
|
|||
- `import {default as x} from "mod"`
|
||||
- Everything:
|
||||
- `import * as obj from "mod"`
|
||||
- Only fetch/evalute the module, don't import:
|
||||
- Import the module (it runs), but do not assign it to a variable:
|
||||
- `import "mod"`
|
||||
|
||||
We can put import/export statements below or after other code, that doesn't matter.
|
||||
We can put import/export statements at the top or at the bottom of a script, that doesn't matter.
|
||||
|
||||
So this is technically fine:
|
||||
```js
|
||||
sayHi();
|
||||
|
||||
import {sayHi} from './say.js'; // import at the end of the file
|
||||
// ...
|
||||
|
||||
import {sayHi} from './say.js'; // import at the end of the script
|
||||
```
|
||||
|
||||
In practice imports are usually at the start of the file, but that's only for better convenience.
|
||||
|
|
|
@ -35,9 +35,9 @@ Then after any changes, the `callback` is executed, with a list of [MutationReco
|
|||
[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),
|
||||
- `"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,
|
||||
|
@ -48,7 +48,7 @@ Then after any changes, the `callback` is executed, with a list of [MutationReco
|
|||
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>
|
||||
<div contentEditable id="elem">Click and <b>edit</b>, please</div>
|
||||
|
||||
<script>
|
||||
let observer = new MutationObserver(mutationRecords => {
|
||||
|
@ -98,11 +98,11 @@ mutationRecords = [{
|
|||
|
||||
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.
|
||||
We can track something like `contentEditable` and implement "undo/redo" functionality (record mutations and rollback/redo them on demand). There are also cases when `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.
|
||||
Let's say we're making a website about programming. Naturally, articles and other materials may contain source code snippets.
|
||||
|
||||
An HTML code snippet looks like this:
|
||||
An HTML markup of a code snippet looks like this:
|
||||
```html
|
||||
...
|
||||
<pre class="language-javascript"><code>
|
||||
|
@ -112,9 +112,9 @@ An HTML code snippet looks like this:
|
|||
...
|
||||
```
|
||||
|
||||
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.
|
||||
Also we'll use a JavaScript highlighting library on our site, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds into them special tags and styles for colored syntax highlighting, similar to what you see in examples here, at 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:
|
||||
When to run that method? We can do it on `DOMContentLoaded` event, or at the bottom of the page. At that moment we have DOM ready, can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them:
|
||||
|
||||
```js
|
||||
// highlight all code snippets on the page
|
||||
|
@ -139,7 +139,7 @@ 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?**
|
||||
**Where and when to call `Prism.highlightElem` for a dynamically loaded article?**
|
||||
|
||||
We could append that call to the code that loads an article, like this:
|
||||
|
||||
|
@ -153,9 +153,9 @@ 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.
|
||||
...But imagine, we have many places in the code where we load contents: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? That's not very convenient, and also easy to forget.
|
||||
|
||||
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.
|
||||
And what if the content is loaded by a third-party module? 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.
|
||||
|
||||
|
@ -176,7 +176,7 @@ let observer = new MutationObserver(mutations => {
|
|||
// examine new nodes
|
||||
|
||||
for(let node of mutation.addedNodes) {
|
||||
// skip newly added text nodes
|
||||
// we track only elements, skip other nodes (e.g. text nodes)
|
||||
if (!(node instanceof HTMLElement)) continue;
|
||||
|
||||
// check the inserted element for being a code snippet
|
||||
|
@ -184,7 +184,7 @@ let observer = new MutationObserver(mutations => {
|
|||
Prism.highlightElement(node);
|
||||
}
|
||||
|
||||
// search its subtree for code snippets
|
||||
// maybe there's a code snippet somewhere in its subtree?
|
||||
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
|
||||
Prism.highlightElement(elem);
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ 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:
|
||||
The code below populates `innerHTML`. Please run the code above first, it will watch and highlight the new content:
|
||||
|
||||
```js run
|
||||
let demoElem = document.getElementById('highlight-demo');
|
||||
|
@ -217,12 +217,9 @@ demoElem.innerHTML = `A code snippet is below:
|
|||
|
||||
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.
|
||||
|
||||
## Additional methods
|
||||
|
||||
## 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:
|
||||
There's a method to stop observing the node:
|
||||
|
||||
- `observer.disconnect()` -- stops the observation.
|
||||
|
||||
|
@ -240,6 +237,10 @@ let mutationRecords = observer.takeRecords();
|
|||
observer.disconnect();
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## Summary
|
||||
|
||||
`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content.
|
||||
|
|
|
@ -106,7 +106,7 @@ drawHtmlTree(node3, 'div.domtree', 690, 150);
|
|||
|
||||
While generating the DOM, browsers automatically process errors in the document, close tags and so on.
|
||||
|
||||
Such an "invalid" document:
|
||||
Such an document with unclosed tags:
|
||||
|
||||
```html no-beautify
|
||||
<p>Hello
|
||||
|
@ -225,7 +225,7 @@ The best way to study them is to click around. Most values are editable in-place
|
|||
|
||||
## Interaction with console
|
||||
|
||||
As we explore the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see how it looks. Here are few tips to travel between the Elements tab and the console.
|
||||
As we explore the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see the result. Here are few tips to travel between the Elements tab and the console.
|
||||
|
||||
- Select the first `<li>` in the Elements tab.
|
||||
- Press `key:Esc` -- it will open console right below the Elements tab.
|
||||
|
|
|
@ -160,9 +160,9 @@ button.onclick = sayThanks;
|
|||
button.onclick = sayThanks();
|
||||
```
|
||||
|
||||
If we add brackets, then `sayThanks()` -- will be the *result* of the function execution, so `onclick` in the last code becomes `undefined` (the function returns nothing). That won't work.
|
||||
If we add parentheses, `sayThanks()` -- is a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work.
|
||||
|
||||
...But in the markup we do need the brackets:
|
||||
...But in the markup we do need the parentheses:
|
||||
|
||||
```html
|
||||
<input type="button" id="button" onclick="sayThanks()">
|
||||
|
@ -351,7 +351,7 @@ Some properties of `event` object:
|
|||
: Event type, here it's `"click"`.
|
||||
|
||||
`event.currentTarget`
|
||||
: Element that handled the event. That's exactly the same as `this`, unless you bind `this` to something else, and then `event.currentTarget` becomes useful.
|
||||
: Element that handled the event. That's exactly the same as `this`, unless the handler is an arrow function, or its `this` is bound to something else, then `event.currentTarget` becomes useful.
|
||||
|
||||
`event.clientX / event.clientY`
|
||||
: Window-relative coordinates of the cursor, for mouse events.
|
||||
|
|
|
@ -75,13 +75,11 @@ Please note that `event.code` specifies exactly which key is pressed. For instan
|
|||
|
||||
Let's say, we want to handle a hotkey: `key:Ctrl+Z` (or `key:Cmd+Z` for Mac). Most text editors hook the "Undo" action on it. We can set a listener on `keydown` and check which key is pressed -- to detect when we have the hotkey.
|
||||
|
||||
Please answer the question -- in such a listener, should we check the value of `event.key` or `event.code`?
|
||||
There's a dilemma here: in such a listener, should we check the value of `event.key` or `event.code`?
|
||||
|
||||
Please, pause and answer.
|
||||
On one hand, the value of `event.key` changes depending on the language. If the visitor has several languages in OS and switches between them, the same key gives different characters. So it makes sense to check `event.code`, it's always the same.
|
||||
|
||||
Made up your mind?
|
||||
|
||||
If you've got an understanding, then the answer is, of course, `event.code`, as we don't want `event.key` there. The value of `event.key` can change depending on the language or `CapsLock` enabled. The value of `event.code` is strictly bound to the key, so here we go:
|
||||
Like this:
|
||||
|
||||
```js run
|
||||
document.addEventListener('keydown', function(event) {
|
||||
|
@ -91,11 +89,30 @@ document.addEventListener('keydown', function(event) {
|
|||
});
|
||||
```
|
||||
|
||||
On the other hand, there's a problem with `event.code`. For different keyboard layouts, the same key may have different labels (letters).
|
||||
|
||||
For example, here are US layout ("QWERTY") and German layout ("QWERTZ") under it (courtesy of Wikipedia):
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
For the same key, US layout has "Z", while German layout has "Y" (letters are swapped).
|
||||
|
||||
So, `event.code` will equal `KeyZ` for people with German layout when they press "Y".
|
||||
|
||||
That sounds odd, but so it is. The [specification](https://www.w3.org/TR/uievents-code/#table-key-code-alphanumeric-writing-system) explicitly mentions such behavior.
|
||||
|
||||
- `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch.
|
||||
- `event.code` may match a wrong character for unexpected layout. Same letters in different layouts may map to different physical keys, leading to different codes. That happens for several codes, e.g. `keyA`, `keyQ`, `keyZ` (as we've seen). You can find the list in the [specification](https://www.w3.org/TR/uievents-code/#table-key-code-alphanumeric-writing-system).
|
||||
|
||||
So, to reliably track layout-dependent characters, `event.key` may be a better way.
|
||||
|
||||
## Auto-repeat
|
||||
|
||||
If a key is being pressed for a long enough time, it starts to repeat: the `keydown` triggers again and again, and then when it's released we finally get `keyup`. So it's kind of normal to have many `keydown` and a single `keyup`.
|
||||
If a key is being pressed for a long enough time, it starts to "auto-repeat": the `keydown` triggers again and again, and then when it's released we finally get `keyup`. So it's kind of normal to have many `keydown` and a single `keyup`.
|
||||
|
||||
For all repeating keys the event object has `event.repeat` property set to `true`.
|
||||
For events triggered by auto-repeat, the event object has `event.repeat` property set to `true`.
|
||||
|
||||
|
||||
## Default actions
|
||||
|
@ -146,10 +163,9 @@ Now arrows and deletion works well.
|
|||
|
||||
In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `which` properties of the event object.
|
||||
|
||||
There were so many browser incompatibilities that developers of the specification decided to deprecate all of them. The old code still works, as the browser keep supporting them, but there's totally no need to use those any more.
|
||||
|
||||
There was time when this chapter included their detailed description. But as of now we can forget about those.
|
||||
There were so many browser incompatibilities that developers of the specification decided to deprecate all of them. The old code still works, as browsers keep supporting them, but there's totally no need to use those any more.
|
||||
|
||||
There was a time when this chapter included their detailed description. But as of now it was removed and replaced with more details about the modern event handling.
|
||||
|
||||
## Summary
|
||||
|
||||
|
@ -165,6 +181,6 @@ Main keyboard event properties:
|
|||
- `code` -- the "key code" (`"KeyA"`, `"ArrowLeft"` and so on), specific to the physical location of the key on keyboard.
|
||||
- `key` -- the character (`"A"`, `"a"` and so on), for non-character keys usually has the same value as `code`.
|
||||
|
||||
In the past, keyboard events were sometimes used to track user input in form fields. That's not reliable, because the input can come from various sources. We have `input` and `change` events to handle any input (covered later in the chapter <info:events-change-input>). They trigger after any input, including mouse or speech recognition.
|
||||
In the past, keyboard events were sometimes used to track user input in form fields. That's not reliable, because the input can come from various sources. We have `input` and `change` events to handle any input (covered later in the chapter <info:events-change-input>). They trigger after any kind of input, including copy-pasting or speech recognition.
|
||||
|
||||
We should use keyboard events when we really want keyboard. For example, to react on hotkeys or special keys.
|
||||
|
|
BIN
2-ui/3-event-details/5-keyboard-events/german-layout.png
Normal file
BIN
2-ui/3-event-details/5-keyboard-events/german-layout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
2-ui/3-event-details/5-keyboard-events/german-layout@2x.png
Normal file
BIN
2-ui/3-event-details/5-keyboard-events/german-layout@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
2-ui/3-event-details/5-keyboard-events/us-layout.png
Normal file
BIN
2-ui/3-event-details/5-keyboard-events/us-layout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
2-ui/3-event-details/5-keyboard-events/us-layout@2x.png
Normal file
BIN
2-ui/3-event-details/5-keyboard-events/us-layout@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -92,7 +92,7 @@ The `defer` attribute is ignored if the script has no `src`.
|
|||
|
||||
## async
|
||||
|
||||
The `async` attribute means that a script is completely independant:
|
||||
The `async` attribute means that a script is completely independent:
|
||||
|
||||
- The page doesn't wait for async scripts, the contents is processed and displayed.
|
||||
- `DOMContentLoaded` and async scripts don't wait each other:
|
||||
|
@ -120,7 +120,7 @@ So, if we have several `async` scripts, they may execute in any order. Whatever
|
|||
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.
|
||||
Async scripts are great when we integrate an independent third-party script into the page: counters, ads and so on.
|
||||
|
||||
```html
|
||||
<script async src="https://google-analytics.com/analytics.js"></script>
|
||||
|
|
|
@ -9,7 +9,7 @@ Although, there's a bit of confusion, because there are many classes. To name a
|
|||
|
||||
Binary data in JavaScript is implemented in a non-standard way, compared to other languages. But when we sort things out, everything becomes fairly simple.
|
||||
|
||||
**The basic binary object is `ArrayBuffer` -- a reference to a fixed-length contiguos memory area.**
|
||||
**The basic binary object is `ArrayBuffer` -- a reference to a fixed-length contiguous memory area.**
|
||||
|
||||
We create it like this:
|
||||
```js run
|
||||
|
@ -209,7 +209,7 @@ These methods allow us to copy typed arrays, mix them, create new arrays from ex
|
|||
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format.
|
||||
|
||||
- For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`.
|
||||
- With `DataView` we access the data with methods like `.getUint8(i)` or `.gteUint16(i)`. We choose the format at method call time instead of the construction time.
|
||||
- With `DataView` we access the data with methods like `.getUint8(i)` or `.getUint16(i)`. We choose the format at method call time instead of the construction time.
|
||||
|
||||
The syntax:
|
||||
|
||||
|
@ -249,7 +249,7 @@ dataView.setUint32(0, 0); // set 4-byte number to zero
|
|||
To do almost any operation on `ArrayBuffer`, we need a view.
|
||||
|
||||
- It can be a `TypedArray`:
|
||||
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
|
||||
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for unsigned integers of 8, 16, and 32 bits.
|
||||
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment.
|
||||
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
|
||||
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
|
||||
|
@ -261,7 +261,7 @@ There are also two additional terms:
|
|||
- `ArrayBufferView` is an umbrella term for all these kinds of views.
|
||||
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
|
||||
|
||||
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common teerms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
|
||||
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common terms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
|
||||
|
||||
|
||||
Here's a cheatsheet:
|
||||
|
|
|
@ -312,7 +312,7 @@ socket.onmessage = function(event) {
|
|||
}
|
||||
```
|
||||
|
||||
Server-side code is a little bit beyound our scope here. We're using browser WebSocket API, a server may have another library.
|
||||
Server-side code is a little bit beyond our scope here. We're using browser WebSocket API, a server may have another library.
|
||||
|
||||
Still it can also be pretty simple. We'll use Node.js with <https://github.com/websockets/ws> module for websockets.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
# Template element
|
||||
|
||||
A built-in `<template>` element serves as a storage for markup. The browser ignores it contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.
|
||||
A built-in `<template>` element serves as a storage for HTML markup templates. The browser ignores it contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.
|
||||
|
||||
In theory, we could create any invisible element somewhere in HTML for markup storage purposes. What's special about `<template>`?
|
||||
In theory, we could create any invisible element somewhere in HTML for HTML markup storage purposes. What's special about `<template>`?
|
||||
|
||||
First, its content can be any valid HTML, even if it normally requires a proper enclosing tag.
|
||||
|
||||
|
@ -31,9 +31,9 @@ We can put styles and scripts into `<template>` as well:
|
|||
</template>
|
||||
```
|
||||
|
||||
The browser considers `<template>` content "out of the document", so the style is not applied, scripts are not executed, `<video autoplay>` is not run, etc.
|
||||
The browser considers `<template>` content "out of the document": styles are not applied, scripts are not executed, `<video autoplay>` is not run, etc.
|
||||
|
||||
The content becomes live (the script executes) when we insert it.
|
||||
The content becomes live (styles apply, scripts run etc) when we insert it into the document.
|
||||
|
||||
## Inserting template
|
||||
|
||||
|
@ -87,7 +87,7 @@ Let's rewrite a Shadow DOM example from the previous chapter using `<template>`:
|
|||
</script>
|
||||
```
|
||||
|
||||
In the line `(*)` when we clone and insert `tmpl.content`, its children (`<style>`, `<p>`) are inserted instead.
|
||||
In the line `(*)` when we clone and insert `tmpl.content`, as it's `DocumentFragment`, its children (`<style>`, `<p>`) are inserted instead.
|
||||
|
||||
They form the shadow DOM:
|
||||
|
||||
|
@ -109,8 +109,8 @@ To summarize:
|
|||
|
||||
The `<template>` tag is quite unique, because:
|
||||
|
||||
- The browser checks the syntax inside it (as opposed to using a template string inside a script).
|
||||
- The browser checks HTML syntax inside it (as opposed to using a template string inside a script).
|
||||
- ...But still allows to use any top-level HTML tags, even those that don't make sense without proper wrappers (e.g. `<tr>`).
|
||||
- The content becomes interactive: scripts run, `<video autoplay>` plays etc, when inserted into the document.
|
||||
|
||||
The `<template>` tag does not feature any sophisticated iteration mechanisms, data binding or variable substitutions, making it less powerful than frameworks. But we can build those on top of it.
|
||||
The `<template>` element does not feature any iteration mechanisms, data binding or variable substitutions, but we can implement those on top of it.
|
||||
|
|
|
@ -12,4 +12,4 @@ let str = '<> <a href="/"> <input type="radio" checked> <b>';
|
|||
alert( str.match(reg) ); // '<a href="/">', '<input type="radio" checked>', '<b>'
|
||||
```
|
||||
|
||||
Let's assume that may not contain `<` and `>` inside (in quotes too), that simplifies things a bit.
|
||||
Here we assume that tag attributes may not contain `<` and `>` (inside squotes too), that simplifies things a bit.
|
||||
|
|
|
@ -264,7 +264,7 @@ That's what's going on:
|
|||
2. Then it looks for `pattern:.*?`: takes one character (lazily!), check if there's a match for `pattern:" class="doc">` (none).
|
||||
3. Then takes another character into `pattern:.*?`, and so on... until it finally reaches `match:" class="doc">`.
|
||||
|
||||
But the problem is: that's already beyound the link, in another tag `<p>`. Not what we want.
|
||||
But the problem is: that's already beyond the link, in another tag `<p>`. Not what we want.
|
||||
|
||||
Here's the picture of the match aligned with the text:
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ If we forget the `u` flag and occasionally use surrogate pairs, then we can get
|
|||
|
||||
Normally, regexps understand `[a-z]` as a "range of characters with codes between codes of `a` and `z`.
|
||||
|
||||
But without `u` flag, surrogate pairs are assumed to be a "pair of independant characters", so `[𝒳-𝒴]` is like `[<55349><56499>-<55349><56500>]` (replaced each surrogate pair with code points). Now we can clearly see that the range `56499-55349` is unacceptable, as the left range border must be less than the right one.
|
||||
But without `u` flag, surrogate pairs are assumed to be a "pair of independent characters", so `[𝒳-𝒴]` is like `[<55349><56499>-<55349><56500>]` (replaced each surrogate pair with code points). Now we can clearly see that the range `56499-55349` is unacceptable, as the left range border must be less than the right one.
|
||||
|
||||
Using the `u` flag makes it work right:
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ There are also other derived categories, like:
|
|||
For instance, let's look for a 6-digit hex number:
|
||||
|
||||
```js run
|
||||
let reg = /\p{Hex_Digit}{6}/u; // flag 'u' is requireds
|
||||
let reg = /\p{Hex_Digit}{6}/u; // flag 'u' is required
|
||||
|
||||
alert("color: #123ABC".match(reg)); // 123ABC
|
||||
```
|
||||
|
|
|
@ -7,7 +7,7 @@ One of common tasks for regexps is "parsing": when we get a text and analyze it
|
|||
|
||||
For instance, there are HTML parsers for browser pages, that turn text into a structured document. There are parsers for programming languages, like JavaScript, etc.
|
||||
|
||||
Writing parsers is a special area, with its own tools and algorithms, so we don't go deep in there, but there's a very common question: "What is the text at the given position?".
|
||||
Writing parsers is a special area, with its own tools and algorithms, so we don't go deep in there, but there's a very common question in them, and, generally, for text analysis: "What kind of entity is at the given position?".
|
||||
|
||||
For instance, for a programming language variants can be like:
|
||||
- Is it a "name" `pattern:\w+`?
|
||||
|
@ -15,9 +15,13 @@ For instance, for a programming language variants can be like:
|
|||
- Or an operator `pattern:[+-/*]`?
|
||||
- (a syntax error if it's not anything in the expected list)
|
||||
|
||||
In JavaScript, to perform a search starting from a given position, we can use `regexp.exec` with `regexp.lastIndex` property, but that's not what we need!
|
||||
So, we should try to match a couple of regular expressions, and make a decision what's at the given position.
|
||||
|
||||
We'd like to check the match exactly at given position, not "starting" from it.
|
||||
In JavaScript, how can we perform a search starting from a given position? Regular calls start searching from the text start.
|
||||
|
||||
We'd like to avoid creating substrings, as this slows down the execution considerably.
|
||||
|
||||
One option is to use `regexp.exec` with `regexp.lastIndex` property, but that's not what we need, as this would search the text starting from `lastIndex`, while we only need to text the match *exactly* at the given position.
|
||||
|
||||
Here's a (failing) attempt to use `lastIndex`:
|
||||
|
||||
|
@ -33,13 +37,11 @@ alert (regexp.exec(str)); // function
|
|||
|
||||
The match is found, because `regexp.exec` starts to search from the given position and goes on by the text, successfully matching "function" later.
|
||||
|
||||
We could work around that by checking if "`regexp.exec(str).index` property is `5`, and if not, ignore the much. But the main problem here is performance.
|
||||
|
||||
The regexp engine does a lot of unnecessary work by scanning at further positions. The delays are clearly noticeable if the text is long, because there are many such searches in a parser.
|
||||
We could work around that by checking if "`regexp.exec(str).index` property is `5`, and if not, ignore the match. But the main problem here is performance. The regexp engine does a lot of unnecessary work by scanning at further positions. The delays are clearly noticeable if the text is long, because there are many such searches in a parser.
|
||||
|
||||
## The "y" flag
|
||||
|
||||
So we've came to the problem: how to search for a match, starting exactly at the given position.
|
||||
So we've came to the problem: how to search for a match exactly at the given position.
|
||||
|
||||
That's what `y` flag does. It makes the regexp search only at the `lastIndex` position.
|
||||
|
||||
|
@ -66,6 +68,6 @@ As we can see, now the regexp is only matched at the given position.
|
|||
|
||||
So what `y` does is truly unique, and very important for writing parsers.
|
||||
|
||||
The `y` flag allows to apply a regular expression (or many of them one-by-one) exactly at the given position and when we understand what's there, we can move on -- step by step examining the text.
|
||||
The `y` flag allows to test a regular expression exactly at the given position and when we understand what's there, we can move on -- step by step examining the text.
|
||||
|
||||
Without the flag the regexp engine always searches till the end of the text, that takes time, especially if the text is large. So our parser would be very slow. The `y` flag is exactly the right thing here.
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
|
||||
# Attribution-NonCommercial-ShareAlike 4.0
|
||||
The tutorial is free to read.
|
||||
|
||||
If you'd like to do something else with it, please get a permission from Ilya Kantor, iliakan@javascript.ru.
|
||||
|
||||
As of now, we license the tutorial to almost everyone for free under the terms of an "open" CC-BY-NC-SA license. Just please be so kind to contact me.
|
||||
|
||||
## Attribution-NonCommercial-ShareAlike license (CC-BY-NC-SA)
|
||||
|
||||
The full license text is at <https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode>.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue