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)`?
|
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.
|
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.
|
||||||
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.
|
||||||
|
|
||||||
As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
|
|
||||||
|
|
||||||
## ToPrimitive
|
## 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.
|
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.
|
||||||
|
|
||||||
Depending on the context, the conversion has a so-called "hint".
|
|
||||||
|
|
||||||
There are three variants:
|
There are three variants:
|
||||||
|
|
||||||
`"string"`
|
`"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
|
```js
|
||||||
// output
|
// output
|
||||||
|
@ -35,7 +31,7 @@ There are three variants:
|
||||||
```
|
```
|
||||||
|
|
||||||
`"number"`
|
`"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
|
```js
|
||||||
// explicit conversion
|
// explicit conversion
|
||||||
|
@ -52,7 +48,7 @@ There are three variants:
|
||||||
`"default"`
|
`"default"`
|
||||||
: Occurs in rare cases when the operator is "not sure" what type to expect.
|
: 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
|
```js
|
||||||
// binary plus
|
// 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.
|
In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions.
|
||||||
|
|
||||||
|
## Return types
|
||||||
## ToPrimitive and ToString/ToNumber
|
|
||||||
|
|
||||||
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
|
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".
|
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.
|
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)
|
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
|
## Summary
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
# Arrays
|
# Arrays
|
||||||
|
|
||||||
Objects allow you to store keyed collections of values. That's fine.
|
Objects allow you to store keyed collections of values. That's fine.
|
||||||
|
|
||||||
But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc.
|
But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc.
|
||||||
|
|
||||||
It is not convenient to use an object here, because it provides no methods to manage the order of elements. We can’t insert a new property “between” the existing ones. Objects are just not meant for such use.
|
It is not convenient to use an object here, because it provides no methods to manage the order of elements. We can’t insert a new property “between” the existing ones. Objects are just not meant for such use.
|
||||||
|
|
||||||
There exists a special data structure named `Array`, to store ordered collections.
|
There exists a special data structure named `Array`, to store ordered collections.
|
||||||
|
|
||||||
## Declaration
|
## Declaration
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ arr[3](); // hello
|
||||||
|
|
||||||
````smart header="Trailing comma"
|
````smart header="Trailing comma"
|
||||||
An array, just like an object, may end with a comma:
|
An array, just like an object, may end with a comma:
|
||||||
```js
|
```js
|
||||||
let fruits = [
|
let fruits = [
|
||||||
"Apple",
|
"Apple",
|
||||||
"Orange",
|
"Orange",
|
||||||
"Plum"*!*,*/!*
|
"Plum"*!*,*/!*
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
@ -106,7 +106,7 @@ Arrays support both operations.
|
||||||
|
|
||||||
In practice we need it very often. For example, a queue of messages that need to be shown on-screen.
|
In practice we need it very often. For example, a queue of messages that need to be shown on-screen.
|
||||||
|
|
||||||
There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
|
There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
|
||||||
|
|
||||||
It supports two operations:
|
It supports two operations:
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to
|
||||||
|
|
||||||
For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).
|
For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).
|
||||||
|
|
||||||
Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.
|
Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.
|
||||||
|
|
||||||
In computer science the data structure that allows it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
|
In computer science the data structure that allows it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
|
||||||
|
|
||||||
|
@ -189,11 +189,11 @@ alert( fruits );
|
||||||
|
|
||||||
## Internals
|
## 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.
|
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.
|
||||||
|
|
||||||
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
|
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
|
||||||
|
|
||||||
For instance, it is copied by reference:
|
For instance, it is copied by reference:
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ let fruits = ["Banana"]
|
||||||
let arr = fruits; // copy by reference (two variables reference the same array)
|
let arr = fruits; // copy by reference (two variables reference the same array)
|
||||||
|
|
||||||
alert( arr === fruits ); // true
|
alert( arr === fruits ); // true
|
||||||
|
|
||||||
arr.push("Pear"); // modify the array by reference
|
arr.push("Pear"); // modify the array by reference
|
||||||
|
|
||||||
alert( fruits ); // Banana, Pear - 2 items now
|
alert( fruits ); // Banana, Pear - 2 items now
|
||||||
|
@ -229,7 +229,7 @@ But the engine will see that we're working with the array as with a regular obje
|
||||||
|
|
||||||
The ways to misuse an array:
|
The ways to misuse an array:
|
||||||
|
|
||||||
- Add a non-numeric property like `arr.test = 5`.
|
- Add a non-numeric property like `arr.test = 5`.
|
||||||
- Make holes, like: add `arr[0]` and then `arr[1000]` (and nothing between them).
|
- Make holes, like: add `arr[0]` and then `arr[1000]` (and nothing between them).
|
||||||
- Fill the array in the reverse order, like `arr[1000]`, `arr[999]` and so on.
|
- Fill the array in the reverse order, like `arr[1000]`, `arr[999]` and so on.
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ let fruits = ["Apple", "Orange", "Plum"];
|
||||||
|
|
||||||
// iterates over array elements
|
// iterates over array elements
|
||||||
for (let fruit of fruits) {
|
for (let fruit of fruits) {
|
||||||
alert( fruit );
|
alert( fruit );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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.
|
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.
|
Generally, we shouldn't use `for..in` for arrays.
|
||||||
|
|
||||||
|
@ -338,7 +338,7 @@ fruits[123] = "Apple";
|
||||||
alert( fruits.length ); // 124
|
alert( fruits.length ); // 124
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that we usually don't use arrays like that.
|
Note that we usually don't use arrays like that.
|
||||||
|
|
||||||
Another interesting thing about the `length` property is that it's writable.
|
Another interesting thing about the `length` property is that it's writable.
|
||||||
|
|
||||||
|
@ -385,7 +385,7 @@ To evade such surprises, we usually use square brackets, unless we really know w
|
||||||
|
|
||||||
## Multidimensional arrays
|
## 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
|
```js run
|
||||||
let matrix = [
|
let matrix = [
|
||||||
|
@ -445,7 +445,7 @@ Array is a special kind of object, suited to storing and managing ordered data i
|
||||||
|
|
||||||
The call to `new Array(number)` creates an array with the given length, but without elements.
|
The call to `new Array(number)` creates an array with the given length, but without elements.
|
||||||
|
|
||||||
- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods.
|
- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods.
|
||||||
- If we shorten `length` manually, the array is truncated.
|
- If we shorten `length` manually, the array is truncated.
|
||||||
|
|
||||||
We can use an array as a deque with the following operations:
|
We can use an array as a deque with the following operations:
|
||||||
|
@ -461,4 +461,3 @@ To loop over the elements of the array:
|
||||||
- `for (let i in arr)` -- never use.
|
- `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>.
|
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.
|
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.
|
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"
|
```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 getter -- we can read `object.prop`, otherwise we can't.
|
||||||
- If there's a setter -- we can set `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.
|
They only operate on the object itself. Properties from the prototype are taken into account.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
|
- 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.
|
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`:
|
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`.
|
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
|
```js
|
||||||
class Rabbit extends Animal {
|
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.
|
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
|
```js run
|
||||||
let animal = {
|
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.
|
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:
|
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"]
|
[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
|
```html run
|
||||||
<script type="module">
|
<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 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.
|
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() { ... }
|
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
|
```js
|
||||||
// 📁 main.js
|
// 📁 main.js
|
||||||
import {sayHi} from './lib.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'];
|
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.
|
That's fine, because `export default` is only one per file. Contrary to that, omitting a name for named imports would be an error:
|
||||||
Contrary to that, omitting a name for named imports would be an error:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
export class { // Error! (non-default export needs a name)
|
export class { // Error! (non-default export needs a name)
|
||||||
|
@ -229,9 +228,9 @@ export class { // Error! (non-default export needs a name)
|
||||||
|
|
||||||
### "Default" alias
|
### "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
|
```js
|
||||||
function sayHi(user) {
|
function sayHi(user) {
|
||||||
|
@ -278,7 +277,7 @@ new User('John');
|
||||||
|
|
||||||
### Should I use default exports?
|
### 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.
|
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
|
```js
|
||||||
import {User} from './user.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
|
```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.
|
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"`
|
- `import {default as x} from "mod"`
|
||||||
- Everything:
|
- Everything:
|
||||||
- `import * as obj from "mod"`
|
- `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"`
|
- `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:
|
So this is technically fine:
|
||||||
```js
|
```js
|
||||||
sayHi();
|
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.
|
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:
|
[MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects have properties:
|
||||||
|
|
||||||
- `type` -- mutation type, one of
|
- `type` -- mutation type, one of
|
||||||
- `"attributes"` (attribute modified)
|
- `"attributes"`: attribute modified
|
||||||
- `"characterData"` (data modified)
|
- `"characterData"`: data modified
|
||||||
- `"childList"` (elements added/removed),
|
- `"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,
|
- `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,
|
- `addedNodes/removedNodes` -- nodes that were added/removed,
|
||||||
- `previousSibling/nextSibling` -- the previous and next sibling to added/removed nodes,
|
- `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.
|
For example, here's a `<div>` with `contentEditable` attribute. That attribute allows us to focus on it and edit.
|
||||||
|
|
||||||
```html run
|
```html run
|
||||||
<div contentEditable id="elem">Edit <b>me</b>, please</div>
|
<div contentEditable id="elem">Click and <b>edit</b>, please</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let observer = new MutationObserver(mutationRecords => {
|
let observer = new MutationObserver(mutationRecords => {
|
||||||
|
@ -98,11 +98,11 @@ mutationRecords = [{
|
||||||
|
|
||||||
When `MutationObserver` is needed? Is there a scenario when such thing can be useful?
|
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
|
```html
|
||||||
...
|
...
|
||||||
<pre class="language-javascript"><code>
|
<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
|
```js
|
||||||
// highlight all code snippets on the page
|
// 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.
|
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:
|
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.
|
Luckily, there's another option.
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ let observer = new MutationObserver(mutations => {
|
||||||
// examine new nodes
|
// examine new nodes
|
||||||
|
|
||||||
for(let node of mutation.addedNodes) {
|
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;
|
if (!(node instanceof HTMLElement)) continue;
|
||||||
|
|
||||||
// check the inserted element for being a code snippet
|
// check the inserted element for being a code snippet
|
||||||
|
@ -184,7 +184,7 @@ let observer = new MutationObserver(mutations => {
|
||||||
Prism.highlightElement(node);
|
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-"]')) {
|
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
|
||||||
Prism.highlightElement(elem);
|
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>
|
<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
|
```js run
|
||||||
let demoElem = document.getElementById('highlight-demo');
|
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.
|
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
|
There's a method to stop observing the node:
|
||||||
|
|
||||||
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.
|
- `observer.disconnect()` -- stops the observation.
|
||||||
|
|
||||||
|
@ -240,6 +237,10 @@ let mutationRecords = observer.takeRecords();
|
||||||
observer.disconnect();
|
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
|
## Summary
|
||||||
|
|
||||||
`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content.
|
`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.
|
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
|
```html no-beautify
|
||||||
<p>Hello
|
<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
|
## 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.
|
- Select the first `<li>` in the Elements tab.
|
||||||
- Press `key:Esc` -- it will open console right below 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();
|
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
|
```html
|
||||||
<input type="button" id="button" onclick="sayThanks()">
|
<input type="button" id="button" onclick="sayThanks()">
|
||||||
|
@ -351,7 +351,7 @@ Some properties of `event` object:
|
||||||
: Event type, here it's `"click"`.
|
: Event type, here it's `"click"`.
|
||||||
|
|
||||||
`event.currentTarget`
|
`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`
|
`event.clientX / event.clientY`
|
||||||
: Window-relative coordinates of the cursor, for mouse events.
|
: 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.
|
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?
|
Like this:
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
document.addEventListener('keydown', function(event) {
|
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
|
## 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
|
## 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.
|
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 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 time when this chapter included their detailed description. But as of now we can forget about those.
|
|
||||||
|
|
||||||
|
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
|
## 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.
|
- `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`.
|
- `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.
|
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
|
## 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.
|
- The page doesn't wait for async scripts, the contents is processed and displayed.
|
||||||
- `DOMContentLoaded` and async scripts don't wait each other:
|
- `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.
|
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.
|
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
|
```html
|
||||||
<script async src="https://google-analytics.com/analytics.js"></script>
|
<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.
|
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:
|
We create it like this:
|
||||||
```js run
|
```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.
|
[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]`.
|
- 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:
|
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.
|
To do almost any operation on `ArrayBuffer`, we need a view.
|
||||||
|
|
||||||
- It can be a `TypedArray`:
|
- 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.
|
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment.
|
||||||
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
|
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
|
||||||
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
|
- `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.
|
- `ArrayBufferView` is an umbrella term for all these kinds of views.
|
||||||
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
|
- `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:
|
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.
|
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
|
# 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.
|
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>
|
</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
|
## Inserting template
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ Let's rewrite a Shadow DOM example from the previous chapter using `<template>`:
|
||||||
</script>
|
</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:
|
They form the shadow DOM:
|
||||||
|
|
||||||
|
@ -109,8 +109,8 @@ To summarize:
|
||||||
|
|
||||||
The `<template>` tag is quite unique, because:
|
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>`).
|
- ...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 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>'
|
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).
|
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">`.
|
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:
|
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`.
|
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:
|
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:
|
For instance, let's look for a 6-digit hex number:
|
||||||
|
|
||||||
```js run
|
```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
|
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.
|
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:
|
For instance, for a programming language variants can be like:
|
||||||
- Is it a "name" `pattern:\w+`?
|
- Is it a "name" `pattern:\w+`?
|
||||||
|
@ -15,9 +15,13 @@ For instance, for a programming language variants can be like:
|
||||||
- Or an operator `pattern:[+-/*]`?
|
- Or an operator `pattern:[+-/*]`?
|
||||||
- (a syntax error if it's not anything in the expected list)
|
- (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`:
|
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.
|
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.
|
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 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
|
## 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.
|
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.
|
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.
|
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>.
|
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