improvements

This commit is contained in:
Ilya Kantor 2019-07-16 19:23:00 +03:00
parent 67e317cee9
commit 52ef3256c9
16 changed files with 100 additions and 96 deletions

View file

@ -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`.

View file

@ -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.

View file

@ -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":
![](generateSequence-1.png)
@ -60,7 +62,7 @@ As of now, we got the first value only:
![](generateSequence-2.png)
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.

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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.

View file

@ -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