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

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