Proxy
what’s a proxy
a proxy is a object with a superpower of wrapping another object Like a trash wrapper
Not only wrap it but also protect it . The protecting way is to intercept the operations that are about to operate on the object that been wrapped inside For example reading/writing properties
optionally handling them on Proxy’s own
Proxy syntax
1 | let` proxy `=` `new` `Proxy``(`target`,` handler`) |
target– is an object to wrap, can be anything, including functions.handler– proxy configuration: an object with “traps”, methods that intercept operations. – e.g.gettrap for reading a property oftarget,settrap for writing a property intotarget, and so on.
For operations on proxy, if there’s a corresponding trap in handler, then it runs
otherwise the operation is performed directly on target.
A proxy with no “traps” can be seen as the object(inside) itself OR without any traps, proxy is a transparent wrapper around target
Below is the demonstration
1 | let target = {}; |
As there are no traps, all operations on proxy are forwarded to target.
What can we intercept
For a object there are some so-called “internal method”[[Get]], the internal method to read a property,[[Set]], the internal method to write a property,
Proxy traps intercept invocations of these methods
They are listed in the Proxy specification and in the table below.
Internal Method and handler methods and Trigger-point
For every internal method, there’s a trap in this table: the name of the method that we can add to the handler parameter of new Proxy to intercept the operation
To note
handler is a paramter in Proxy’s constructor.It’s a set including all the methods(we can call them Handler Methods)
And what’s actually intercepting the specific operation?THE corresponding handler method!
How?When you trigger the internal method in THE object.
| Internal Method In object 内部方法 | Handler Method In proxy 处理程序方法 | Triggers when… 触发时… |
|---|---|---|
[[Get]] |
get |
reading a property 读取属性 use [ ] or . on a object |
[[Set]] |
set |
writing to a property 写入属性 .newproperty = /.push(for array) /unshift |
[[HasProperty]] |
has |
in operator in 运算符 |
[[Delete]] |
deleteProperty |
delete operator delete 运算符 |
[[Call]] |
apply |
function call 函数调用 |
[[Construct]] |
construct |
new operator new 运算符 |
[[GetPrototypeOf]] |
getPrototypeOf |
Object.getPrototypeOf 对象.getPrototypeOf |
[[SetPrototypeOf]] |
setPrototypeOf |
Object.setPrototypeOf 对象.setPrototypeOf |
[[IsExtensible]] |
isExtensible |
Object.isExtensible 对象可扩展 |
[[PreventExtensions]] |
preventExtensions |
Object.preventExtensions 对象.preventExtensions |
[[DefineOwnProperty]] |
defineProperty |
Object.defineProperty, Object.defineProperties 对象.defineProperty, 对象.defineProperties |
[[GetOwnProperty]] |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor, for..in, Object.keys/values/entries Object.getOwnPropertyDescriptor, for..in , Object.keys/values/entries |
[[OwnPropertyKeys]] |
ownKeys |
Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object.keys/values/entries Object.getOwnPropertyNames、Object.getOwnPropertySymbols、 for..in 、 Object.keys/values/entries |
Notification
[[Set]]must returntrueif the value was written successfully, otherwisefalse.[[Delete]]must returntrueif the value was deleted successfully, otherwisefalse.
How to set the handler
To intercept reading, the handler should have the so-called handler-method get(target, property, receiver) As we mentioned: It triggers when a property is read
target– is the target object, the one passed as the first argument tonew Proxy,property– the object’s property(the key in a key-value pair),receiver– if the target property is a getter, thenreceiveris the object that’s going to be used asthisin its call. Usually that’s theproxyobject itself (or an object that inherits from it, if we inherit from proxy). Right now we don’t need this argument, so it will be explained in more detail later.
eg1
Usually when one tries to get a non-existing array item, they get undefined, but we’ll wrap a regular array into the proxy that traps reading and returns 0 if there’s no such property:
1 | let numbers = [1, 2, 3]; |
there should be a supplement For array : if array is seen as a object {key:value}The 数组的索引 is the key The 具体数值 是 value
eg2
Let’s say we want an array exclusively for numbers. 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 tonew Proxy,property– property name,value– property value,receiver– similar togettrap, matters only for setter properties.
1 | let numbers = []; |
For set, it must return true for a successful write.
Note : Only interact with the proxy object
You should follow below:
When a proxy object is created As for when you have proxied an object
You shouldn’t reference the origin(or target) object.(the object is past the moment you get the proxy version)
And below is a normal way we deal with it.
1 | dictionary = new Proxy(dictionary, ...); |
can overwrite the “dictionary” variable .
Why
Usually When you created a proxy You got to devise some traps . And so it’s important for you to make sure the defined behaviors and traps are constantly applied to ensure all operation go through the proxy and its traps
iteration
[[OwnPropertyKeys]] internal method
Object.keys, for..in loop and most other methods that iterate over object properties (use [[OwnPropertyKeys]] internal method) (intercepted by ownKeys trap) to** get a list of properties**
Such methods differ in details:
Object.getOwnPropertyNames(obj)returns non-symbol keys.Object.getOwnPropertySymbols(obj)returns symbol keys.Object.keys/values()returns non-symbol keys/values withenumerableflag (property flags were explained in the article Property flags and descriptors).for..inloops over non-symbol keys withenumerableflag, and also prototype keys.
to supplement:
1 | Object.keys():这个方法返回一个数组,该数组包含对象自身(不包括原型链)的所有可枚举属性的名称。例如: |
eg3
In the example below we use ownKeys trap to make for..in loop over user, and also Object.keys and Object.values, to skip properties starting with an underscore `_
1 | let user = { |
To articulate
No matter “for in” “Object.keys” or “Object.values”
Deep inside They still use the same list offered by [[OwnPropertyKeys]]
Different methods just filter the list with different ways. And “ownKeys” trap does something to the list (above:Object.keys(target).filter(key => !key.startsWith(‘_‘));)
before the “for in” “Object.keys” or “Object.values” can access the list.
To note
if we return a key that doesn’t exist in the object, Object.keys won’t list it:
1 | let user = {}; |
the trap return a list full of keys But none of these keys exist in the “user “object
Why? The reason is simple: Object.keys returns only properties with the enumerable flag.
the properties of [‘a’, ‘b’, ‘c’] don’t have the flag But how does Object.keys find out?
Object.keys check for it by calling the internal method [[GetOwnProperty]] of “user” for every property to get its descriptor
And here, as there’s no property in user, its descriptor is empty, no enumerable flag, so the listing-properties procedure is skipped.
1 | let user = {}; |
when Object.keys is checking by calling the internal method [[GetOwnProperty]]of “user”
the calling will be intercepted and Object.keys will receive the informing-letter(haha) which is written “it’s enumable”
Convention
- There’s a widespread convention that properties and methods prefixed by an underscore
_are internal. They shouldn’t be accessed from outside the object. - (prop can be seen as a key in the wrapped object’s key-value pair)
- target in “get(target,prop)” is the original object
deleteProperty
1 | let user = { |
eg4
Let’s use proxies to prevent any access to properties starting with _.
We’ll need these traps to accomplish an internal property:
getto throw an error when reading such property,setto throw an error when writing,deletePropertyto throw an error when deleting,ownKeysto exclude properties starting with_fromfor..inand methods likeObject.keys.
here is how we approach that:
1 | let user = { |
To explain:
get: the throwing-error part is obvious But why do we need to return a function binded to the object “target”when what we try to use[[Get]] to get is a function
Because:
In real-life code there will absolutely be a method in the original object that can access the internal value(_ prefixed)1
2
3
4
5
6
7user = {
// ...
checkPassword(value) {
// object method must be able to read _password
return value === this._password;
},
};for example above should be working properly! But with the trap “get” is set already when you write user.checkPassword “this._password” will activate the trap which will deny your access So the checkPassword won’t be working.
After you set “value.bind(target)” actually you are bonding the context of the method(signing “this” to the) to target (we know it’s the original object) And in the target(the original object) there are no traps at all.
To be more clear : You need to ensure when “checkPassword” is working its(“checkPassword”) “this” should point to the “obedient”(no traps)object to ensure it (“checkPassword”) functions!(起作用)
That solution usually works, but isn’t ideal
- the rest is simple
In to has trap
As you can see above(in the internal method and handler method). The has trap intercept the “in” operator(which operate on the proxy object)because the in operator will invoke the [[HasProperty]]
eg5
We’d like to use the in operator to check that a number is in range.
1 | let range = { |
Wrapping functions:”apply”
You should see the article Decortor and forwarding,call/apply first to understand this section