Proxy & Reflect

Proxy

let proxy = new Proxy(target, handler)

  1. target: is an object to wrap, can be anything, including functions.
  2. handler: proxy configuration, an object with “traps”, methods that intercept operations. – e.g. get trap for reading a property of target, set trap for writing a property into target…

empty handler

  • As there are no traps, all operations on proxy are forwarded to target.
    1. writing operation proxy.test= sets the value on target
    2. reading operation proxy.test returns the value from target
    3. for(key in proxy) returns values from target

      traps

      Internal Method Handler Method Triggers when…
      [[Get]] get reading a property
      [[Set]] set writing to a property
      [[HasProperty]] has in operator
      [[Delete]] deleteProperty delete operator
      [[Call]] apply function call
      [[Construct]] construct new operator
      [[GetPrototypeOf]] getPrototypeOf Object.getPrototypeOf
      [[SetPrototypeOf]] setPrototypeOf Object.setPrototypeOf
      [[IsExtensible]] isExtensible Object.isExtensible
      [[PreventExtensions]] preventExtensions Object.preventExtensions
      [[DefineOwnProperty]] defineProperty Object.defineProperty, Object.defineProperties
      [[GetOwnProperty]] getOwnPropertyDescriptor Object.getOwnPropertyDescriptor, for..in, Object.keys/values/entries
      [[OwnPropertyKeys]] ownKeys Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object/keys/values/entries

default value with get trap

To intercept reading, the handler should have a method get(target, property, receiver).

  1. target: is the target object, the one passed as the first argument to new Proxy(target, handler).
  2. item: property name
  3. receiver: proxy object itself.
  • We can use Proxy to implement any logic for “default” values.
    1. non-existing array item returns 0 instead of undefined.
    2. non-existing key-value returns defaultParse instead of undefined. e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let dictionary = {
'Hello': 'Hola',
'Bye': 'Adiós'
};

dictionary = new Proxy(dictionary, {
get(target, item) { // intercept reading a property from dictionary
if (item in target) { // if we have it in the dictionary
return target[item]; // return the translation
} else {
// otherwise, return the non-translated item
return item;
}
}
});

alert( dictionary['Hello'] ); // Hola
alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (no translation)

Validation with “set” trap

To intercept setting sth, let’s say we want an array exclusively for numbers. If a value of another type is added, there should be an error.
set(target, property, value, receiver)

  1. target: is the target object, the one passed as the first argument to new Proxy(target, handler).
  2. property: property name
  3. value: property value
  4. receiver: proxy object itself, matters only for setter properties.
  • The set trap should return true if setting is successful, and false otherwise (triggers TypeError).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let numbers = [];

numbers = new Proxy(numbers, { // (*)
set(target, prop, val) { // to intercept property writing
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
});

numbers.push(1); // added successfully
numbers.push("test"); // TypeError ('set' on proxy returned false)

Don’t forget to return true

There are invariants (conditions that must be fulfilled by internal methods and traps) to be held.
e.g. set must return true/false
delete must return true if the value was deleted successfully…

Built-in func of arrays ins still working

proxy doesn’t break anything.

Iteration with ownKeys and getOwnPropertyDescriptor

ownKeys

Object.keys, for..in loop and most other methods that iterate over object properties use [[OwnPropertyKeys]] (intercepted by ownKeys trap)

  1. Object.getOwnPropertyNames(obj) returns non-symbol keys.
  2. Object.getOwnPropertySymbols(obj) returns symbol keys.
  3. Object.keys/values() returns non-symbol keys/values with enumerable flag.
  4. for..in loops over non-symbol keys with enumerable flag + prototype keys.
  • We can use ownKeys trap to make iteration, like for...in loop, Object.keys, Object.values work differently.
  • Let’s say skip properties starting with an underscore _
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let user = {
name: "John",
age: 30,
_password: "***"
};

user = new Proxy(user, {
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});

// "ownKeys" filters out _password
for(let key in user) alert(key); // name, age
alert( Object.keys(user) ); // name,age
alert( Object.values(user) ); // John,30

getOwnPropertyDescriptor

We can use the trap getOwnPropertyDescriptor to intercept calls to [[GetOwnProperty]], and return a descriptor with enumerable: true.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
let user = { };

user = new Proxy(user, {
ownKeys(target) { // called once to get a list of properties
return ['a', 'b', 'c'];
},

getOwnPropertyDescriptor(target, prop) { // called for every property
return {
enumerable: true,
configurable: true
/* ...other flags, probable "value:..." */
};
}
});

alert( Object.keys(user) ); // a, b, c
```

## Protected properties with `deleteProperty`
We can use proxies to prevent any access to properties starting with `_`, that conventionally means internal properties and methods.
1. `get` to throw an error when reading such property,
2. `set` to throw an error when writing,
3. `deleteProperty` to throw an error when deleting,
4. `ownKeys` to exclude properties starting with `_` from `for..in` and methods like `Object.keys`.

```js
let user = {
name: "John",
_password: "***"
};

user = new Proxy(user, {
get(target, prop) {
if (prop.startsWith('_')) {
throw new Error("Access denied");
}
let value = target[prop];
return (typeof value === 'function') ? value.bind(target) : value; // (*)
},
set(target, prop, val) { // to intercept property writing
if (prop.startsWith('_')) {
throw new Error("Access denied");
} else {
target[prop] = val;
return true;
}
},
deleteProperty(target, prop) { // to intercept property deletion
if (prop.startsWith('_')) {
throw new Error("Access denied");
} else {
delete target[prop];
return true;
}
},
ownKeys(target) { // to intercept property list
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});

in range with has trap

Wrapping functions: apply

Reflect

Proxy limitations

Revocable proxies

文章目录
  1. 1. Proxy
    1. 1.1. empty handler
    2. 1.2. traps
    3. 1.3. default value with get trap
    4. 1.4. Validation with “set” trap
    5. 1.5. Iteration with ownKeys and getOwnPropertyDescriptor
      1. 1.5.1. ownKeys
      2. 1.5.2. getOwnPropertyDescriptor
    6. 1.6. in range with has trap
    7. 1.7. Wrapping functions: apply
    8. 1.8. Reflect
  2. 2. Proxy limitations
  3. 3. Revocable proxies