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.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -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".
|
||||
|
||||
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).
|
||||
|
||||
|
@ -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`.
|
||||
|
||||
|
|
|
@ -35,4 +35,4 @@ alert(generator()); // 282475249
|
|||
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
|
||||
// "generator function" creates "generator object"
|
||||
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
|
||||
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))
|
||||
|
||||
## 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`.
|
||||
|
||||
|
@ -163,7 +165,7 @@ let range = {
|
|||
from: 1,
|
||||
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]() {
|
||||
// ...it returns the iterator object:
|
||||
// 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
|
||||
```
|
||||
|
||||
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
|
||||
function* generateSequence(start, end) {
|
||||
|
@ -201,11 +203,11 @@ let sequence = [...generateSequence(1,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
|
||||
|
||||
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
|
||||
let range = {
|
||||
|
@ -224,19 +226,18 @@ alert( [...range] ); // 1,2,3,4,5
|
|||
|
||||
The `range` object is now iterable.
|
||||
|
||||
That works pretty well, because when `range[Symbol.iterator]` is called:
|
||||
- it returns an object (now a generator)
|
||||
- that has `.next()` method (yep, a generator has it)
|
||||
- that returns values in the form `{value: ..., done: true/false}` (check, exactly what generator does).
|
||||
That works, because `range[Symbol.iterator]()` now returns a generator, and generator methods are exactly what `for..of` expects:
|
||||
- it has `.next()` method
|
||||
- that returns values in the form `{value: ..., done: true/false}`
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
@ -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 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.
|
||||
|
||||
|
@ -285,7 +286,7 @@ for(let code of generatePasswordCodes()) {
|
|||
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:
|
||||
|
||||
|
@ -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.
|
||||
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
|
||||
// resume the generator after some time
|
||||
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:
|
||||
|
||||
|
@ -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.
|
||||
- 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
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# 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:
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.
|
||||
|
||||
```js run
|
||||
|
|
|
@ -20,10 +20,8 @@ user.observe((key, value) => {
|
|||
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.
|
||||
|
||||
|
||||
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)`.
|
||||
|
||||
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`,
|
||||
- `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.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
```js
|
||||
|
@ -175,21 +175,21 @@ dictionary = new Proxy(dictionary, ...);
|
|||
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
|
||||
|
||||
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)`
|
||||
|
||||
- `target` -- is the target object, the one passed as the first argument to `new Proxy`,
|
||||
- `property` -- property name,
|
||||
- `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`).
|
||||
|
||||
|
@ -238,7 +238,7 @@ If it returns a falsy value (or doesn't return anything), that triggers `TypeErr
|
|||
|
||||
## 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:
|
||||
|
||||
|
@ -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:
|
||||
|
||||
- `target` is the target object,
|
||||
- `target` is the target object (function is an object in JavaScript),
|
||||
- `thisArg` is the value of `this`.
|
||||
- `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);
|
||||
|
||||
*!*
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
|
@ -529,14 +528,14 @@ let name = user.name; // GET name
|
|||
user.name = "Pete"; // SET name TO Pete
|
||||
```
|
||||
|
||||
- `Reflect.get` gets the property, like `target[prop]` that we used before.
|
||||
- `Reflect.set` sets the property, like `target[prop] = value`, and also ensures the correct return value.
|
||||
- `Reflect.get` gets the property, like `target[prop]`.
|
||||
- `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.
|
||||
|
||||
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
|
||||
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".
|
||||
|
||||
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!
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
*!*
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue