improvements
This commit is contained in:
parent
67e317cee9
commit
52ef3256c9
16 changed files with 100 additions and 96 deletions
|
@ -314,7 +314,7 @@ if (str.indexOf("Widget") != -1) {
|
||||||
|
|
||||||
One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation.
|
One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation.
|
||||||
|
|
||||||
For 32-bit integers the call `~n` means exactly the same as `-(n+1)` (due to IEEE-754 format).
|
In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
|
@ -345,7 +345,7 @@ It is usually not recommended to use language features in a non-obvious way, but
|
||||||
|
|
||||||
Just remember: `if (~str.indexOf(...))` reads as "if found".
|
Just remember: `if (~str.indexOf(...))` reads as "if found".
|
||||||
|
|
||||||
Technically speaking, numbers are truncated to 32 bits by `~` operator, so there exist other big numbers that give `0`, the smallest is `~4294967295=0`. That makes such check is correct only if a string is not that long.
|
To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check is correct only if a string is not that long.
|
||||||
|
|
||||||
Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below).
|
Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below).
|
||||||
|
|
||||||
|
@ -519,7 +519,7 @@ alert( str );
|
||||||
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
|
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
|
||||||
```
|
```
|
||||||
|
|
||||||
See? Capital characters go first, then a few special ones, then lowercase characters.
|
See? Capital characters go first, then a few special ones, then lowercase characters, and `Ö` near the end of the output.
|
||||||
|
|
||||||
Now it becomes obvious why `a > Z`.
|
Now it becomes obvious why `a > Z`.
|
||||||
|
|
||||||
|
|
|
@ -35,4 +35,4 @@ alert(generator()); // 282475249
|
||||||
alert(generator()); // 1622650073
|
alert(generator()); // 1622650073
|
||||||
```
|
```
|
||||||
|
|
||||||
That's fine for this context. But then we loose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere.
|
That also works. But then we loose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere.
|
||||||
|
|
|
@ -19,14 +19,16 @@ function* generateSequence() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When `generateSequence()` is called, it does not execute the code. Instead, it returns a special object, called "generator".
|
The term "generator function" is a bit misleading, because when called it does not execute the code. Instead, it returns a special object, called "generator object".
|
||||||
|
|
||||||
|
So it's kind of a "generator constructor".
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// "generator function" creates "generator object"
|
// "generator function" creates "generator object"
|
||||||
let generator = generateSequence();
|
let generator = generateSequence();
|
||||||
```
|
```
|
||||||
|
|
||||||
The `generator` object can be perceived as a "frozen function call":
|
The `generator` object is something like an "frozen function call":
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ As of now, we got the first value only:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Let's call `generator.next()` again. It resumes the execution and returns the next `yield`:
|
Let's call `generator.next()` again. It resumes the code execution and returns the next `yield`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let two = generator.next();
|
let two = generator.next();
|
||||||
|
@ -152,7 +154,7 @@ alert(sequence); // 0, 1, 2, 3
|
||||||
|
|
||||||
In the code above, `...generateSequence()` turns the iterable into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator))
|
In the code above, `...generateSequence()` turns the iterable into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator))
|
||||||
|
|
||||||
## Using generators instead of iterables
|
## Using generators for iterables
|
||||||
|
|
||||||
Some time ago, in the chapter [](info:iterable) we created an iterable `range` object that returns values `from..to`.
|
Some time ago, in the chapter [](info:iterable) we created an iterable `range` object that returns values `from..to`.
|
||||||
|
|
||||||
|
@ -163,7 +165,7 @@ let range = {
|
||||||
from: 1,
|
from: 1,
|
||||||
to: 5,
|
to: 5,
|
||||||
|
|
||||||
// for..of calls this method once in the very beginning
|
// for..of range calls this method once in the very beginning
|
||||||
[Symbol.iterator]() {
|
[Symbol.iterator]() {
|
||||||
// ...it returns the iterator object:
|
// ...it returns the iterator object:
|
||||||
// onward, for..of works only with that object, asking it for next values
|
// onward, for..of works only with that object, asking it for next values
|
||||||
|
@ -187,7 +189,7 @@ let range = {
|
||||||
alert([...range]); // 1,2,3,4,5
|
alert([...range]); // 1,2,3,4,5
|
||||||
```
|
```
|
||||||
|
|
||||||
Using a generator to make iterable sequences is so much more elegant:
|
Using a generator to make iterable sequences is simpler and much more elegant:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
function* generateSequence(start, end) {
|
function* generateSequence(start, end) {
|
||||||
|
@ -201,11 +203,11 @@ let sequence = [...generateSequence(1,5)];
|
||||||
alert(sequence); // 1, 2, 3, 4, 5
|
alert(sequence); // 1, 2, 3, 4, 5
|
||||||
```
|
```
|
||||||
|
|
||||||
...But what if we'd like to keep a custom `range` object?
|
|
||||||
|
|
||||||
## Converting Symbol.iterator to generator
|
## Converting Symbol.iterator to generator
|
||||||
|
|
||||||
We can get the best from both worlds by providing a generator as `Symbol.iterator`:
|
We can add generator-style iteration to any custom object by providing a generator as `Symbol.iterator`.
|
||||||
|
|
||||||
|
Here's the same `range`, but with a much more compact iterator:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let range = {
|
let range = {
|
||||||
|
@ -224,19 +226,18 @@ alert( [...range] ); // 1,2,3,4,5
|
||||||
|
|
||||||
The `range` object is now iterable.
|
The `range` object is now iterable.
|
||||||
|
|
||||||
That works pretty well, because when `range[Symbol.iterator]` is called:
|
That works, because `range[Symbol.iterator]()` now returns a generator, and generator methods are exactly what `for..of` expects:
|
||||||
- it returns an object (now a generator)
|
- it has `.next()` method
|
||||||
- that has `.next()` method (yep, a generator has it)
|
- that returns values in the form `{value: ..., done: true/false}`
|
||||||
- that returns values in the form `{value: ..., done: true/false}` (check, exactly what generator does).
|
|
||||||
|
|
||||||
That's not a coincidence, of course. Generators aim to make iterables easier, so we can see that.
|
That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easier.
|
||||||
|
|
||||||
The last variant with a generator is much more concise than the original iterable code, and keeps the same functionality.
|
The last variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality.
|
||||||
|
|
||||||
```smart header="Generators may continue forever"
|
```smart header="Generators may generate values forever"
|
||||||
In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers.
|
In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers.
|
||||||
|
|
||||||
That surely would require a `break` in `for..of`, otherwise the loop would repeat forever and hang.
|
That surely would require a `break` (or `return`) in `for..of` over such generator, otherwise the loop would repeat forever and hang.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generator composition
|
## Generator composition
|
||||||
|
@ -248,7 +249,7 @@ For instance, we'd like to generate a sequence of:
|
||||||
- followed by alphabet letters `a..z` (character codes 65..90)
|
- followed by alphabet letters `a..z` (character codes 65..90)
|
||||||
- followed by uppercased letters `A..Z` (character codes 97..122)
|
- followed by uppercased letters `A..Z` (character codes 97..122)
|
||||||
|
|
||||||
Then we plan to create passwords by selecting characters from it (could add syntax characters as well), but need to generate the sequence first.
|
We can use the sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first.
|
||||||
|
|
||||||
We already have `function* generateSequence(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need.
|
We already have `function* generateSequence(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need.
|
||||||
|
|
||||||
|
@ -285,7 +286,7 @@ for(let code of generatePasswordCodes()) {
|
||||||
alert(str); // 0..9A..Za..z
|
alert(str); // 0..9A..Za..z
|
||||||
```
|
```
|
||||||
|
|
||||||
The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple, it runs generators and transparently forwards their yields outside, as if they were done by the calling generator itself.
|
The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple, `yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator.
|
||||||
|
|
||||||
The result is the same as if we inlined the code from nested generators:
|
The result is the same as if we inlined the code from nested generators:
|
||||||
|
|
||||||
|
@ -357,14 +358,16 @@ generator.next(4); // --> pass the result into the generator
|
||||||
2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code.
|
2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code.
|
||||||
3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`.
|
3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`.
|
||||||
|
|
||||||
Please note, the outer code does not have to immediately call`next(4)`. It may take time to calculate the value. This is also a valid code:
|
Please note, the outer code does not have to immediately call`next(4)`. It may take time to calculate the value. That's not a problem: the generator will resume when the call is made.
|
||||||
|
|
||||||
|
This is also a valid code:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// resume the generator after some time
|
// resume the generator after some time
|
||||||
setTimeout(() => generator.next(4), 1000);
|
setTimeout(() => generator.next(4), 1000);
|
||||||
```
|
```
|
||||||
|
|
||||||
The syntax may seem a bit odd. It's quite uncommon for a function and the calling code to pass values around to each other. But that's exactly what's going on.
|
As we can see, unlike regular functions, generators and the calling code can exchange results by passing values in `next/yield`.
|
||||||
|
|
||||||
To make things more obvious, here's another example, with more calls:
|
To make things more obvious, here's another example, with more calls:
|
||||||
|
|
||||||
|
@ -462,8 +465,8 @@ If we don't catch the error there, then, as usual, it falls through to the outer
|
||||||
- Inside generators (only) there exists a `yield` operator.
|
- Inside generators (only) there exists a `yield` operator.
|
||||||
- The outer code and the generator may exchange results via `next/yield` calls.
|
- The outer code and the generator may exchange results via `next/yield` calls.
|
||||||
|
|
||||||
In modern JavaScript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique.
|
In modern JavaScript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique. And, surely, they are great for making iterable objects.
|
||||||
|
|
||||||
Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data in `for` loop.
|
Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data (e.g paginated fetches over a network) in `for await ... of` loop.
|
||||||
|
|
||||||
In web-programming we often work with streamed data, e.g. need to fetch paginated results, so that's a very important use case.
|
In web-programming we often work with streamed data, so that's another very important use case.
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
|
||||||
# Error on reading non-existant property
|
# Error on reading non-existant property
|
||||||
|
|
||||||
Create a proxy that throws an error for an attempt to read of a non-existant property.
|
Usually, an attempt to read a non-existant property returns `undefined`.
|
||||||
|
|
||||||
|
Create a proxy that throws an error for an attempt to read of a non-existant property instead.
|
||||||
|
|
||||||
That can help to detect programming mistakes early.
|
That can help to detect programming mistakes early.
|
||||||
|
|
||||||
Write a function `wrap(target)` that takes an object `target` and return a proxy instead with that functionality.
|
Write a function `wrap(target)` that takes an object `target` and return a proxy that adds this functionality aspect.
|
||||||
|
|
||||||
That's how it should work:
|
That's how it should work:
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
# Accessing array[-1]
|
# Accessing array[-1]
|
||||||
|
|
||||||
In some languages, we can access array elements using negative indexes, counted from the end.
|
In some programming languages, we can access array elements using negative indexes, counted from the end.
|
||||||
|
|
||||||
Like this:
|
Like this:
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
The solution consists of two parts:
|
The solution consists of two parts:
|
||||||
|
|
||||||
1. Whenever `.observe(handler)` is called, we need to remember the handler somewhere, to be able to call it later. We can store it right in the object, using our symbol as the key.
|
1. Whenever `.observe(handler)` is called, we need to remember the handler somewhere, to be able to call it later. We can store handlers right in the object, using our symbol as the property key.
|
||||||
2. We need a proxy with `set` trap to call handlers in case of any change.
|
2. We need a proxy with `set` trap to call handlers in case of any change.
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
|
|
@ -20,10 +20,8 @@ user.observe((key, value) => {
|
||||||
user.name = "John"; // alerts: SET name=John
|
user.name = "John"; // alerts: SET name=John
|
||||||
```
|
```
|
||||||
|
|
||||||
In other words, an object returned by `makeObservable` has the method `observe(handler)`.
|
In other words, an object returned by `makeObservable` has the method `observe(handler)` that allows to add `handler` function to be called on a property change.
|
||||||
|
|
||||||
Whenever a property changes, `handler(key, value)` is called with the name and value o the property.
|
Whenever a property changes, `handler(key, value)` is called with the name and value o the property.
|
||||||
|
|
||||||
|
|
||||||
P.S. In this task, please handle only writing to a property. Other operations can be implemented in a similar way.
|
P.S. In this task, please handle only writing to a property. Other operations can be implemented in a similar way.
|
||||||
P.P.S. You might want to introduce a global variable or a global structure to store handlers. That's fine here. In real life, such function lives in a module, that has its own global scope.
|
|
||||||
|
|
|
@ -91,11 +91,11 @@ The most common traps are for reading/writing properties.
|
||||||
|
|
||||||
To intercept the reading, the `handler` should have a method `get(target, property, receiver)`.
|
To intercept the reading, the `handler` should have a method `get(target, property, receiver)`.
|
||||||
|
|
||||||
It triggers when a property is read:
|
It triggers when a property is read, with following arguments:
|
||||||
|
|
||||||
- `target` -- is the target object, the one passed as the first argument to `new Proxy`,
|
- `target` -- is the target object, the one passed as the first argument to `new Proxy`,
|
||||||
- `property` -- property name,
|
- `property` -- property name,
|
||||||
- `receiver` -- if the property is a getter, then `receiver` is the object that's going to be used as `this` in that code. Usually that's the `proxy` object itself (or an object that inherits from it, if we inherit from proxy).
|
- `receiver` -- if the target property is a getter, then `receiver` is the object that's going to be used as `this` in its code. Usually that's the `proxy` object itself (or an object that inherits from it, if we inherit from proxy). Right now we don't need this argument, will be explained in more details letter.
|
||||||
|
|
||||||
Let's use `get` to implement default values for an object.
|
Let's use `get` to implement default values for an object.
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (no translation)
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
````smart header="Proxy should be used instead of `target` everywhere"
|
````smart
|
||||||
Please note how the proxy overwrites the variable:
|
Please note how the proxy overwrites the variable:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -175,21 +175,21 @@ dictionary = new Proxy(dictionary, ...);
|
||||||
numbers = new Proxy(numbers, ...);
|
numbers = new Proxy(numbers, ...);
|
||||||
```
|
```
|
||||||
|
|
||||||
The proxy should totally replace the target object everywhere. No one should ever reference the target object after it got proxied. Otherwise it's easy to mess up.
|
The proxy should totally replace the target object everywhere. No one should ever reference the target object after it got proxied. Otherwise it 's easy to mess up.
|
||||||
````
|
````
|
||||||
|
|
||||||
## Validation with "set" trap
|
## Validation with "set" trap
|
||||||
|
|
||||||
Now let's intercept writing as well.
|
Now let's intercept writing as well.
|
||||||
|
|
||||||
Let's say we want a numeric array. If a value of another type is added, there should be an error.
|
Let's say we want a numbers-only array. If a value of another type is added, there should be an error.
|
||||||
|
|
||||||
The `set` trap triggers when a property is written: `set(target, property, value, receiver)`
|
The `set` trap triggers when a property is written: `set(target, property, value, receiver)`
|
||||||
|
|
||||||
- `target` -- is the target object, the one passed as the first argument to `new Proxy`,
|
- `target` -- is the target object, the one passed as the first argument to `new Proxy`,
|
||||||
- `property` -- property name,
|
- `property` -- property name,
|
||||||
- `value` -- property value,
|
- `value` -- property value,
|
||||||
- `receiver` -- same as in `get` trap, only matters if the property is a setter.
|
- `receiver` -- similar to `get` trap, matters only for setter properties.
|
||||||
|
|
||||||
The `set` trap should return `true` if setting is successful, and `false` otherwise (leads to `TypeError`).
|
The `set` trap should return `true` if setting is successful, and `false` otherwise (leads to `TypeError`).
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ If it returns a falsy value (or doesn't return anything), that triggers `TypeErr
|
||||||
|
|
||||||
## Protected properties with "deleteProperty" and "ownKeys"
|
## Protected properties with "deleteProperty" and "ownKeys"
|
||||||
|
|
||||||
There's a widespread convention that properties and methods prefixed by an underscore `_` are internal. They shouldn't be accessible from outside the object.
|
There's a widespread convention that properties and methods prefixed by an underscore `_` are internal. They shouldn't be accessed from outside the object.
|
||||||
|
|
||||||
Technically, that's possible though:
|
Technically, that's possible though:
|
||||||
|
|
||||||
|
@ -409,7 +409,7 @@ We can wrap a proxy around a function as well.
|
||||||
|
|
||||||
The `apply(target, thisArg, args)` trap handles calling a proxy as function:
|
The `apply(target, thisArg, args)` trap handles calling a proxy as function:
|
||||||
|
|
||||||
- `target` is the target object,
|
- `target` is the target object (function is an object in JavaScript),
|
||||||
- `thisArg` is the value of `this`.
|
- `thisArg` is the value of `this`.
|
||||||
- `args` is a list of arguments.
|
- `args` is a list of arguments.
|
||||||
|
|
||||||
|
@ -454,17 +454,16 @@ function sayHi(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
alert(sayHi.length); // 1 (function length is the arguments count)
|
alert(sayHi.length); // 1 (function length is the arguments count in its declaration)
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
sayHi = delay(sayHi, 3000);
|
sayHi = delay(sayHi, 3000);
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
alert(sayHi.length); // 0 (wrapper has no arguments)
|
alert(sayHi.length); // 0 (in the wrapper declaration, there are zero arguments)
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
`Proxy` is much more powerful, as it forwards everything to the target object.
|
`Proxy` is much more powerful, as it forwards everything to the target object.
|
||||||
|
|
||||||
Let's use `Proxy` instead of a wrapping function:
|
Let's use `Proxy` instead of a wrapping function:
|
||||||
|
@ -495,13 +494,13 @@ The result is the same, but now not only calls, but all operations on the proxy
|
||||||
|
|
||||||
We've got a "richer" wrapper.
|
We've got a "richer" wrapper.
|
||||||
|
|
||||||
There exist other traps, but probably you've already got the idea.
|
There exist other traps: the full list is in the beginning of this chapter. Their usage pattern is similar to the above.
|
||||||
|
|
||||||
## Reflect
|
## Reflect
|
||||||
|
|
||||||
The `Reflect` API was designed to work in tandem with `Proxy`.
|
The `Reflect` API was designed to work in tandem with `Proxy`.
|
||||||
|
|
||||||
For every internal object operation that can be trapped, there's a `Reflect` method. It has the same name and arguments as the trap, and can be used to forward the operation to an object.
|
For every internal object operation that can be trapped, there's a `Reflect` method. It has the same name and arguments as the trap, and can be used to forward the operation to an object from the trap.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
@ -529,14 +528,14 @@ let name = user.name; // GET name
|
||||||
user.name = "Pete"; // SET name TO Pete
|
user.name = "Pete"; // SET name TO Pete
|
||||||
```
|
```
|
||||||
|
|
||||||
- `Reflect.get` gets the property, like `target[prop]` that we used before.
|
- `Reflect.get` gets the property, like `target[prop]`.
|
||||||
- `Reflect.set` sets the property, like `target[prop] = value`, and also ensures the correct return value.
|
- `Reflect.set` sets the property, like `target[prop] = value`, and returns `true/false` as needed by `[[Set]]`.
|
||||||
|
|
||||||
In most cases, we can do the same thing without `Reflect`. But we may miss some peculiar aspects.
|
In most cases, we can do the same thing without `Reflect`. But we may miss some peculiar aspects.
|
||||||
|
|
||||||
Consider the following example, it doesn't use `Reflect` and doesn't work right.
|
Consider the following example, it doesn't use `Reflect` and doesn't work right.
|
||||||
|
|
||||||
We have a proxied user object and inherit from it, then use a getter:
|
We have a proxied `user` object with `name` getter and inherit `admin` from it:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let user = {
|
let user = {
|
||||||
|
@ -632,7 +631,7 @@ Still, it's not perfect. There are limitations.
|
||||||
|
|
||||||
Many built-in objects, for example `Map`, `Set`, `Date`, `Promise` and others make use of so-called "internal slots".
|
Many built-in objects, for example `Map`, `Set`, `Date`, `Promise` and others make use of so-called "internal slots".
|
||||||
|
|
||||||
These are like properties, but reserved for internal purposes. Built-in methods access them directly, not via `[[Get]]/[[Set]]` internal methods. So `Proxy` can't intercept that.
|
These are like properties, but reserved for internal, specification-only purposes. Built-in methods access them directly, not via `[[Get]]/[[Set]]` internal methods. So `Proxy` can't intercept that.
|
||||||
|
|
||||||
Who cares? They are internal anyway!
|
Who cares? They are internal anyway!
|
||||||
|
|
||||||
|
@ -779,7 +778,7 @@ A *revocable* proxy is a proxy that can be disabled.
|
||||||
|
|
||||||
Let's say we have a resource, and would like to close access to it any moment.
|
Let's say we have a resource, and would like to close access to it any moment.
|
||||||
|
|
||||||
What we can do is to wrap it into a revocable proxy, without any traps. Such proxy will forward operations to object, and we also get a special method to disable it.
|
What we can do is to wrap it into a revocable proxy, without any traps. Such proxy will forward operations to object, and we can disable it at any moment.
|
||||||
|
|
||||||
The syntax is:
|
The syntax is:
|
||||||
|
|
||||||
|
@ -810,8 +809,7 @@ alert(proxy.data); // Error
|
||||||
|
|
||||||
A call to `revoke()` removes all internal references to the target object from the proxy, so they are no more connected. The target object can be garbage-collected after that.
|
A call to `revoke()` removes all internal references to the target object from the proxy, so they are no more connected. The target object can be garbage-collected after that.
|
||||||
|
|
||||||
We can also store `revoke` in a `WeakMap`, to be able to easily find it by the proxy:
|
We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object:
|
||||||
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
*!*
|
*!*
|
||||||
|
@ -835,7 +833,7 @@ alert(proxy.data); // Error (revoked)
|
||||||
|
|
||||||
The benefit of such approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needeed.
|
The benefit of such approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needeed.
|
||||||
|
|
||||||
Using `WeakMap` instead of `Map` here, because it should not block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory (we don't need its revoke in that case).
|
Using `WeakMap` instead of `Map` here, because it should not block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ function sayHi() {
|
||||||
alert("Hello");
|
alert("Hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
// global functions are accessible as properties of window
|
// global functions are methods of the global objecct:
|
||||||
window.sayHi();
|
window.sayHi();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,17 @@ Also we can generate built-in events like `click`, `mousedown` etc, that may be
|
||||||
|
|
||||||
## Event constructor
|
## Event constructor
|
||||||
|
|
||||||
Events form a hierarchy, just like DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class.
|
Build-in event classes form a hierarchy, similar to DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class.
|
||||||
|
|
||||||
We can create `Event` objects like this:
|
We can create `Event` objects like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let event = new Event(event type[, options]);
|
let event = new Event(type[, options]);
|
||||||
```
|
```
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
- *event type* -- may be any string, like `"click"` or our own like `"hey-ho!"`.
|
- *type* -- event type, a string like `"click"` or our own like `"my-event"`.
|
||||||
- *options* -- the object with two optional properties:
|
- *options* -- the object with two optional properties:
|
||||||
- `bubbles: true/false` -- if `true`, then the event bubbles.
|
- `bubbles: true/false` -- if `true`, then the event bubbles.
|
||||||
- `cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events.
|
- `cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events.
|
||||||
|
@ -66,9 +66,13 @@ All we need is to set `bubbles` to `true`:
|
||||||
// ...dispatch on elem!
|
// ...dispatch on elem!
|
||||||
let event = new Event("hello", {bubbles: true}); // (2)
|
let event = new Event("hello", {bubbles: true}); // (2)
|
||||||
elem.dispatchEvent(event);
|
elem.dispatchEvent(event);
|
||||||
|
|
||||||
|
// the handler on document will activate and display the message.
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
1. We should use `addEventListener` for our custom events, because `on<event>` only exists for built-in events, `document.onhello` doesn't work.
|
1. We should use `addEventListener` for our custom events, because `on<event>` only exists for built-in events, `document.onhello` doesn't work.
|
||||||
|
@ -160,11 +164,9 @@ The event class tells something about "what kind of event" it is, and if the eve
|
||||||
|
|
||||||
We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified.
|
We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified.
|
||||||
|
|
||||||
Of course, if the event has a non-standard name, then it's not known to the browser, and there's no "default browser action" for it.
|
Of course, for custom events, with names unknown for the browser, there are no "default browser actions". But our code may plan its own actions after `dispatchEvent`.
|
||||||
|
|
||||||
But the event-generating code may plan some actions after `dispatchEvent`.
|
The call of `event.preventDefault()` is a way for the handler to send a signal that those actions should be canceled .
|
||||||
|
|
||||||
The call of `event.preventDefault()` is a way for the handler to send a signal that those actions shouldn't be performed.
|
|
||||||
|
|
||||||
In that case the call to `elem.dispatchEvent(event)` returns `false`. And the event-generating code knows that the processing shouldn't continue.
|
In that case the call to `elem.dispatchEvent(event)` returns `false`. And the event-generating code knows that the processing shouldn't continue.
|
||||||
|
|
||||||
|
@ -267,7 +269,7 @@ Now `dispatchEvent` runs asynchronously after the current code execution is fini
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
To generate an event, we first need to create an event object.
|
To generate an event from code, we first need to create an event object.
|
||||||
|
|
||||||
The generic `Event(name, options)` constructor accepts an arbitrary event name and the `options` object with two properties:
|
The generic `Event(name, options)` constructor accepts an arbitrary event name and the `options` object with two properties:
|
||||||
- `bubbles: true` if the event should bubble.
|
- `bubbles: true` if the event should bubble.
|
||||||
|
|
|
@ -113,7 +113,7 @@ For JS-code it means that we should check `if (event.ctrlKey || event.metaKey)`.
|
||||||
|
|
||||||
```warn header="There are also mobile devices"
|
```warn header="There are also mobile devices"
|
||||||
Keyboard combinations are good as an addition to the workflow. So that if the visitor has a
|
Keyboard combinations are good as an addition to the workflow. So that if the visitor has a
|
||||||
keyboard -- it works. And if your device doesn't have it -- then there's another way to do the same.
|
keyboard -- it works. And if their device doesn't have it -- then there should be another way to do the same.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Coordinates: clientX/Y, pageX/Y
|
## Coordinates: clientX/Y, pageX/Y
|
||||||
|
@ -123,7 +123,7 @@ All mouse events have coordinates in two flavours:
|
||||||
1. Window-relative: `clientX` and `clientY`.
|
1. Window-relative: `clientX` and `clientY`.
|
||||||
2. Document-relative: `pageX` and `pageY`.
|
2. Document-relative: `pageX` and `pageY`.
|
||||||
|
|
||||||
For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`. And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is. They are similar to `position:fixed`.
|
For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`. And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is, how far the document was scrolled. They are similar to `position:fixed`.
|
||||||
|
|
||||||
````online
|
````online
|
||||||
Move the mouse over the input field to see `clientX/clientY` (it's in the `iframe`, so coordinates are relative to that `iframe`):
|
Move the mouse over the input field to see `clientX/clientY` (it's in the `iframe`, so coordinates are relative to that `iframe`):
|
||||||
|
@ -138,9 +138,9 @@ Coordinates `pageX`, `pageY` are similar to `position:absolute` on the document
|
||||||
|
|
||||||
You can read more about coordinates in the chapter <info:coordinates>.
|
You can read more about coordinates in the chapter <info:coordinates>.
|
||||||
|
|
||||||
## No selection on mousedown
|
## Disabling selection on mousedown
|
||||||
|
|
||||||
Mouse clicks have a side-effect that may be disturbing. A double click selects the text.
|
Mouse clicks have a side-effect that may be disturbing in some interfaces: a double click selects the text.
|
||||||
|
|
||||||
If we want to handle click events ourselves, then the "extra" selection doesn't look good.
|
If we want to handle click events ourselves, then the "extra" selection doesn't look good.
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ Before...
|
||||||
|
|
||||||
Now the bold element is not selected on double clicks.
|
Now the bold element is not selected on double clicks.
|
||||||
|
|
||||||
The text inside it is still selectable. However, the selection should start not on the text itself, but before or after it. Usually that's fine though.
|
The text inside it is still selectable. However, the selection should start not on the text itself, but before or after it. Usually that's fine for users.
|
||||||
|
|
||||||
````smart header="Canceling the selection"
|
````smart header="Canceling the selection"
|
||||||
Instead of *preventing* the selection, we can cancel it "post-factum" in the event handler.
|
Instead of *preventing* the selection, we can cancel it "post-factum" in the event handler.
|
||||||
|
@ -235,9 +235,11 @@ Mouse events have the following properties:
|
||||||
- Window-relative coordinates: `clientX/clientY`.
|
- Window-relative coordinates: `clientX/clientY`.
|
||||||
- Document-relative coordinates: `pageX/pageY`.
|
- Document-relative coordinates: `pageX/pageY`.
|
||||||
|
|
||||||
It's also important to deal with text selection as an unwanted side-effect of clicks.
|
It's also important to deal with text selection, it may be an unwanted side-effect of clicks.
|
||||||
|
|
||||||
There are several ways to do this, for instance:
|
There are several ways to do this, for instance:
|
||||||
1. The CSS-property `user-select:none` (with browser prefixes) completely disables text-selection.
|
1. The CSS-property `user-select:none` (with browser prefixes) completely disables text-selection.
|
||||||
2. Cancel the selection post-factum using `getSelection().removeAllRanges()`.
|
2. Cancel the selection post-factum using `getSelection().removeAllRanges()`.
|
||||||
3. Handle `mousedown` and prevent the default action (usually the best).
|
3. Handle `mousedown` and prevent the default action (usually the best).
|
||||||
|
|
||||||
|
The selection is a separate topic, covered in another chapter <info:selection-range>.
|
||||||
|
|
|
@ -13,16 +13,14 @@ That is, periodical requests to the server: "Hello, I'm here, do you have any in
|
||||||
In response, the server first takes a notice to itself that the client is online, and second - sends a packet of messages it got till that moment.
|
In response, the server first takes a notice to itself that the client is online, and second - sends a packet of messages it got till that moment.
|
||||||
|
|
||||||
That works, but there are downsides:
|
That works, but there are downsides:
|
||||||
1. Messages are passed with a delay up to 10 seconds.
|
1. Messages are passed with a delay up to 10 seconds (between requests).
|
||||||
2. Even if there are no messages, the server is bombed with requests every 10 seconds. That's quite a load to handle for backend, speaking performance-wise.
|
2. Even if there are no messages, the server is bombed with requests every 10 seconds. That's quite a load to handle for backend, speaking performance-wise.
|
||||||
|
|
||||||
So, if we're talking about a very small service, the approach may be viable.
|
So, if we're talking about a very small service, the approach may be viable, but generally, it needs an improvement.
|
||||||
|
|
||||||
But generally, it needs an improvement.
|
|
||||||
|
|
||||||
## Long polling
|
## Long polling
|
||||||
|
|
||||||
Long polling -- is a better way to poll the server.
|
So-called "long polling" is a much better way to poll the server.
|
||||||
|
|
||||||
It's also very easy to implement, and delivers messages without delays.
|
It's also very easy to implement, and delivers messages without delays.
|
||||||
|
|
||||||
|
@ -37,16 +35,17 @@ The situation when the browser sent a request and has a pending connection with
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Even if the connection is lost, because of, say, a network error, the browser immediately sends a new request.
|
If the connection is lost, because of, say, a network error, the browser immediately sends a new request.
|
||||||
|
|
||||||
A sketch of client-side code:
|
A sketch of client-side `subscribe` function that makes long requests:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
async function subscribe() {
|
async function subscribe() {
|
||||||
let response = await fetch("/subscribe");
|
let response = await fetch("/subscribe");
|
||||||
|
|
||||||
if (response.status == 502) {
|
if (response.status == 502) {
|
||||||
// Connection timeout, happens when the connection was pending for too long
|
// Connection timeout error,
|
||||||
|
// may happen when the connection was pending for too long, and the remote server or a proxy closed it
|
||||||
// let's reconnect
|
// let's reconnect
|
||||||
await subscribe();
|
await subscribe();
|
||||||
} else if (response.status != 200) {
|
} else if (response.status != 200) {
|
||||||
|
@ -66,7 +65,7 @@ async function subscribe() {
|
||||||
subscribe();
|
subscribe();
|
||||||
```
|
```
|
||||||
|
|
||||||
The `subscribe()` function makes a fetch, then waits for the response, handles it and calls itself again.
|
As you can see, `subscribe` function makes a fetch, then waits for the response, handles it and calls itself again.
|
||||||
|
|
||||||
```warn header="Server should be ok with many pending connections"
|
```warn header="Server should be ok with many pending connections"
|
||||||
The server architecture must be able to work with many pending connections.
|
The server architecture must be able to work with many pending connections.
|
||||||
|
|
|
@ -13,6 +13,6 @@ All visitors of this page will see messages of each other.
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
new PublishForm(document.forms.publish, 'publish');
|
new PublishForm(document.forms.publish, 'publish');
|
||||||
// random url to avoid any caching issues
|
// random url parameter to avoid any caching issues
|
||||||
new SubscribePane(document.getElementById('subscribe'), 'subscribe?random=' + Math.random());
|
new SubscribePane(document.getElementById('subscribe'), 'subscribe?random=' + Math.random());
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -75,7 +75,7 @@ if (!module.parent) {
|
||||||
} else {
|
} else {
|
||||||
exports.accept = accept;
|
exports.accept = accept;
|
||||||
|
|
||||||
if (process.send) { // if run by pm2 have this defined
|
if (process.send) {
|
||||||
process.on('message', (msg) => {
|
process.on('message', (msg) => {
|
||||||
if (msg === 'shutdown') {
|
if (msg === 'shutdown') {
|
||||||
close();
|
close();
|
||||||
|
|
|
@ -41,7 +41,7 @@ data: of two lines
|
||||||
- Messages are delimited with double line breaks `\n\n`.
|
- Messages are delimited with double line breaks `\n\n`.
|
||||||
- To send a line break `\n`, we can immediately one more `data:` (3rd message above).
|
- To send a line break `\n`, we can immediately one more `data:` (3rd message above).
|
||||||
|
|
||||||
In practice, complex messages are usually sent JSON-encoded, so line-breaks are encoded within them.
|
In practice, complex messages are usually sent JSON-encoded. Line-breaks are encoded as `\n` within them, so multiline `data:` messages are not necessary.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ data: Hello, I set the reconnection delay to 15 seconds
|
||||||
|
|
||||||
The `retry:` may come both together with some data, or as a standalone message.
|
The `retry:` may come both together with some data, or as a standalone message.
|
||||||
|
|
||||||
The browser should wait that much before reconnect. If the network connection is lost, the browser may wait till it's restored, and then retry.
|
The browser should wait that many milliseconds before reconnect. Or longer, e.g. if the browser knows (from OS) that there's no network connection at the moment, it may wait until the connection appears, and then retry.
|
||||||
|
|
||||||
- If the server wants the browser to stop reconnecting, it should respond with HTTP status 204.
|
- If the server wants the browser to stop reconnecting, it should respond with HTTP status 204.
|
||||||
- If the browser wants to close the connection, it should call `eventSource.close()`:
|
- If the browser wants to close the connection, it should call `eventSource.close()`:
|
||||||
|
@ -116,7 +116,7 @@ eventSource.close();
|
||||||
Also, there will be no reconnection if the response has an incorrect `Content-Type` or its HTTP status differs from 301, 307, 200 and 204. The connection the `"error"` event is emitted, and the browser won't reconnect.
|
Also, there will be no reconnection if the response has an incorrect `Content-Type` or its HTTP status differs from 301, 307, 200 and 204. The connection the `"error"` event is emitted, and the browser won't reconnect.
|
||||||
|
|
||||||
```smart
|
```smart
|
||||||
There's no way to "reopen" a closed connection. If we'd like to connect again, just create a new `EventSource`.
|
When a connection is finally closed, there's no way to "reopen" it. If we'd like to connect again, just create a new `EventSource`.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Message id
|
## Message id
|
||||||
|
@ -143,7 +143,7 @@ When a message with `id:` is received, the browser:
|
||||||
- Upon reconnection sends the header `Last-Event-ID` with that `id`, so that the server may re-send following messages.
|
- Upon reconnection sends the header `Last-Event-ID` with that `id`, so that the server may re-send following messages.
|
||||||
|
|
||||||
```smart header="Put `id:` after `data:`"
|
```smart header="Put `id:` after `data:`"
|
||||||
Please note: the `id:` is appended below the message data, to ensure that `lastEventId` is updated after the message data is received.
|
Please note: the `id` is appended below message `data` by the server, to ensure that `lastEventId` is updated after the message is received.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Connection status: readyState
|
## Connection status: readyState
|
||||||
|
@ -206,17 +206,16 @@ Then the browser automatically reconnects.
|
||||||
|
|
||||||
[codetabs src="eventsource"]
|
[codetabs src="eventsource"]
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
The `EventSource` object communicates with the server. It establishes a persistent connection and allows the server to send messages over it.
|
`EventSource` object automatically establishes a persistent connection and allows the server to send messages over it.
|
||||||
|
|
||||||
It offers:
|
It offers:
|
||||||
- Automatic reconnect, with tunable `retry` timeout.
|
- Automatic reconnect, with tunable `retry` timeout.
|
||||||
- Message ids to resume events, the last identifier is sent in `Last-Event-ID` header.
|
- Message ids to resume events, the last received identifier is sent in `Last-Event-ID` header upon reconnection.
|
||||||
- The current state is in the `readyState` property.
|
- The current state is in the `readyState` property.
|
||||||
|
|
||||||
That makes `EventSource` a viable alternative to `WebSocket`, as it's more low-level and lacks these features.
|
That makes `EventSource` a viable alternative to `WebSocket`, as it's more low-level and lacks such built-in features (though they can be implemented).
|
||||||
|
|
||||||
In many real-life applications, the power of `EventSource` is just enough.
|
In many real-life applications, the power of `EventSource` is just enough.
|
||||||
|
|
||||||
|
@ -262,9 +261,11 @@ The server may set a custom event name in `event:`. Such events should be handle
|
||||||
|
|
||||||
The server sends messages, delimited by `\n\n`.
|
The server sends messages, delimited by `\n\n`.
|
||||||
|
|
||||||
Message parts may start with:
|
A message may have following fields:
|
||||||
|
|
||||||
- `data:` -- message body, a sequence of multiple `data` is interpreted as a single message, with `\n` between the parts.
|
- `data:` -- message body, a sequence of multiple `data` is interpreted as a single message, with `\n` between the parts.
|
||||||
- `id:` -- renews `lastEventId`, sent in `Last-Event-ID` on reconnect.
|
- `id:` -- renews `lastEventId`, sent in `Last-Event-ID` on reconnect.
|
||||||
- `retry:` -- recommends a retry delay for reconnections in ms. There's no way to set it from JavaScript.
|
- `retry:` -- recommends a retry delay for reconnections in ms. There's no way to set it from JavaScript.
|
||||||
- `event:` -- even name, must precede `data:`.
|
- `event:` -- even name, must precede `data:`.
|
||||||
|
|
||||||
|
A message may include one or more fields in any order, but `id:` usually goes the last.
|
||||||
|
|
|
@ -61,7 +61,7 @@ alert( localStorage.test ); // 2
|
||||||
delete localStorage.test;
|
delete localStorage.test;
|
||||||
```
|
```
|
||||||
|
|
||||||
That's allowed for historical reasons, and mostly works, but generally not recommended for two reasons:
|
That's allowed for historical reasons, and mostly works, but generally not recommended, because:
|
||||||
|
|
||||||
1. If the key is user-generated, it can be anything, like `length` or `toString`, or another built-in method of `localStorage`. In that case `getItem/setItem` work fine, while object-like access fails:
|
1. If the key is user-generated, it can be anything, like `length` or `toString`, or another built-in method of `localStorage`. In that case `getItem/setItem` work fine, while object-like access fails:
|
||||||
```js run
|
```js run
|
||||||
|
@ -157,7 +157,7 @@ Properties and methods are the same, but it's much more limited:
|
||||||
|
|
||||||
- The `sessionStorage` exists only within the current browser tab.
|
- The `sessionStorage` exists only within the current browser tab.
|
||||||
- Another tab with the same page will have a different storage.
|
- Another tab with the same page will have a different storage.
|
||||||
- But it is shared between iframes in the tab (assuming they come from the same origin).
|
- But it is shared between iframes in the same tab (assuming they come from the same origin).
|
||||||
- The data survives page refresh, but not closing/opening the tab.
|
- The data survives page refresh, but not closing/opening the tab.
|
||||||
|
|
||||||
Let's see that in action.
|
Let's see that in action.
|
||||||
|
@ -212,7 +212,7 @@ localStorage.setItem('now', Date.now());
|
||||||
|
|
||||||
Please note that the event also contains: `event.url` -- the url of the document where the data was updated.
|
Please note that the event also contains: `event.url` -- the url of the document where the data was updated.
|
||||||
|
|
||||||
Also, `event.storageArea` contains the storage object -- the event is the same for both `sessionStorage` and `localStorage`, so `storageArea` references the one that was modified. We may even want to set something back in it, to "respond" to a change.
|
Also, `event.storageArea` contains the storage object -- the event is the same for both `sessionStorage` and `localStorage`, so `event.storageArea` references the one that was modified. We may even want to set something back in it, to "respond" to a change.
|
||||||
|
|
||||||
**That allows different windows from the same origin to exchange messages.**
|
**That allows different windows from the same origin to exchange messages.**
|
||||||
|
|
||||||
|
@ -245,5 +245,5 @@ API:
|
||||||
Storage event:
|
Storage event:
|
||||||
|
|
||||||
- Triggers on `setItem`, `removeItem`, `clear` calls.
|
- Triggers on `setItem`, `removeItem`, `clear` calls.
|
||||||
- Contains all the data about the operation, the document `url` and the storage object.
|
- Contains all the data about the operation (`key/oldValue/newValue`), the document `url` and the storage object `storageArea`.
|
||||||
- Triggers on all `window` objects that have access to the storage except the one that generated it (within a tab for `sessionStorage`, globally for `localStorage`).
|
- Triggers on all `window` objects that have access to the storage except the one that generated it (within a tab for `sessionStorage`, globally for `localStorage`).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue