This paper briefly expounds a little metaprogramming knowledge, and then gives a more detailed Proxy usage (at least more detailed than MDN, supplemented with specific examples of various error situations, and more accurate than the above machine), and then uses some examples to describe Proxy suitable for what scenarios to use
metaprogramming
First, there are two levels of programming:
- Base level/application level: code handles user input
- yuanLevel: Code that deals with the base level of code
The word meta here means: something about something itself, so the word metaprogramming means programming about programming. Metaprogramming can be done in two of the same languages. The languages in which metaprogramming is written are called metalanguages. The language of the program being manipulated is called the “target language”, where JavaScript is the meta-language and Java is the target language:
const str = 'Hello' + '! '.repeat(3);
console.log('System.out.println("'+str+'"');
Copy the code
The ability of a programming language to be its own meta-language is called Reflection and is used to discover and tweak the structure and semantics of your application.
Metaprogramming comes in three forms:
- Introspection: Reveals the state of the program at runtime, capturing the structure of the program in read-only form
- Such as the use
Object.keys(obj)
Wait, there’s a new one on the ES6Reflect
Many interfaces for capturing internal state are consolidated and unified
- Such as the use
- Self-modification: Modifies the program structure/data at run time
- Such as
delete
或property descriptors
等
- Such as
- Intercession: Allows for redefining the semantics of certain language operations
- For example, in this article
Proxy
- For example, in this article
Metaprogramming and Reflect
ES6 also adds a new global object named Reflect, most of which already exist in other forms, to unify its interface:
- In the past, the various methods of obtaining/modifying the runtime state of a program were usually scattered or hung
Object.prototype
On, some onFunction.prototype
On, there is an operator (e.gdelete
/in
Etc.) - Previous calls were too complex or insecure
- Call it down in some cases
obj.hasOwnProperty
The method may not exist on the object (for example, the object is passedObject.create(null)
Created), so used at this timeObject.prototype.hasOwnProperty.call
It’s the safest, but it’s too complicated call
和apply
There are also the above problems
- Call it down in some cases
- Return value is not scientific, as used
Object.defineProperty
, returns an object on success, otherwise throws oneTypeError
Therefore, it has to be usedtry... catch
To catch any errors that occur while defining properties. whileReflect.defineProperty
Returns a Boolean success status, so you can use onlyif... else
Check out the article here to see what Reflect has done
Basic content of Proxy
Finally, let’s look at the Proxy constructor:
Proxy(target, handler)
Copy the code
target
:Proxy
The wrapped target object (which can be any type of object, including a native array, a function, or even another proxy).handler
: processor object (proxy's handler
) with a variety of proxyable operations from the defined proxy object. Among them are numeroustraps
Invariant (Invariants)
Before delving further into what proxies do, let’s review how to pass protected objects:
- Non-extensible
- Cannot add attributes and cannot change stereotypes
'use strict'
const obj = Object.preventExtensions({});
console.log(Object.isExtensible(obj)); // false
obj.foo = 123; // Cannot add property foo, object is not extensible
Object.setPrototypeOf(obj, null) // #<Object> is not extensible
Copy the code
- Do not write (Non – writable)
value
Cannot be changed by the assignment operator
- Non-configurable (non-64x)
- Properties cannot be changed/deleted (except for the
writable
Instead offalse
)
- Properties cannot be changed/deleted (except for the
Using a Proxy, it is easy to violate the above constraints (because these constraints apply to Proxy objects, but Proxy objects are not bound by them). So the Proxy checks for us on calls/returns, casts truish and falsish to Boolean when we expect Boolean, etc. Further explanations and examples are provided in the constraints section below.
Here’s a document on invariants
Then let’s look at what the Proxy handler provides for us to use
handler.get()
Intercepts read property operations on objects.
get: function(target, property, receiver) {}
Copy the code
target
: Target object.property
: Name of the property to be obtained.receiver
: The object that was called initially. Is usuallyproxy
Itself, buthandler
的get
Methods can also be invoked indirectly on the prototype chain or in other ways (so not necessarilyproxy
Itself).- Here,
target
和property
It’s all perfectly understandable, butreceiver
For extra attention, here’s an example to help you understand:
- Here,
var obj = {
myObj: 1
};
obj.__proto__ = new Proxy({
test: 123}, {get:function(target, property, receiver) {
console.log(target, property, receiver);
return 1; }});console.log(obj.test);
// {test: 123}, "test" ,{myObj: 1}
// You can see that receiver is the object that is called initially
Copy the code
This method intercepts the following operations on the target object:
- Access properties:
proxy[foo]
和proxy.bar
- Access properties on the prototype chain:
Object.create(proxy)[foo]
Reflect.get()
Constraint (violation of the constraint raises a Type Error) :
- If the target property to be accessed is not writable and configurable, the value returned must be the same as that of the target property.
const obj = {};
// Not writable and not configurable
Object.defineProperty(obj, "a", {
configurable: false.enumerable: true.value: 10.writable: false
});
const p = new Proxy(obj, {
get: function(target, prop) {
return 20; }});console.log(p.a); // 'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')
Copy the code
- If the properties of the target object are not configurable and are not defined
get
Method, its return value must beundefined
const obj = { a: 10 };
// Cannot be configured and get is not defined
Object.defineProperty(obj, "a", {
configurable: false.get: undefined});const p = new Proxy(obj, {
get: function(target, prop) {
return 20; }});console.log(p.a) // 'get' on proxy: property 'a' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '20')
Copy the code
handler.set()
Intercepts an operation to set a property value
set: function(target, property, value, receiver) {}
Copy the code
target
: Target object.property
: Specifies the name of the property to be set.value
: New value to be setreceiver
: The object that was called initially. With the aboveget
In thereceiver
The return value:
The set method should return a Boolean:
- return
true
This indicates that setting the property succeeded - return
false
If the set property action occurs in strict mode, one is thrownTypeError
Note: Most methods in Proyx will essentially convert the return value to Boolean, so you can return whatever you want inside and get a Boolean outside; That’s why the words “truish” and “falsish” are used in errors
This method intercepts the following operations on the target object:
- Specify attribute values:
proxy[foo] = bar
和proxy.foo = bar
- Specify the inheritor’s property value:
Object.create(proxy)[foo] = bar
Reflect.set()
Constraints:
- If the target property is not writable and configurable, its value cannot be changed.
const obj = {};
// Not writable and not configurable
Object.defineProperty(obj, "a", {
configurable: false.enumerable: true.value: 10.writable: false
});
const p = new Proxy(obj, {
set: function(target, prop, value, receiver) {
console.log("called: " + prop + "=" + value);
return true; }}); p.a =20; // trap returned truish for property 'a' which exists in the proxy target as a non-configurable and non-writable data property with a different value
// Note that we are not really changing the value of 'a', this error is caused by return true
Copy the code
- If the properties of the target object are not configurable and are not defined
set
Method, cannot set its value.
const obj = {};
// Unwritable and set not defined
Object.defineProperty(obj, "a", {
configurable: false.set: undefined
});
const p = new Proxy(obj, {
set: function(target, prop, value, receiver) {
console.log("called: " + prop + "=" + value);
return true; }}); p.a =20; // trap returned truish for property 'a' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter
// Note that we are not really changing the value of 'a', this error is caused by return true
Copy the code
- In strict mode, if
set
Method returnsfalse
, will throw oneTypeError
The exception.
'use strict'
const obj = {};
const p = new Proxy(obj, {
set: function(target, prop, value, receiver) {
console.log("called: " + prop + "=" + value);
return false; }}); p.a =20; // trap returned falsish for property 'a'
Copy the code
handler.apply()
Intercepting function calls
apply: function(target, thisArg, argumentsList) {}
Copy the code
target
: Target object (function).thisArg
: The context object when called.argumentsList
: The array of arguments when called.
This method intercepts the following operations on the target object:
proxy(... args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
Constraints:
target
Must itself be callable. That is, it must be a function object.
handler.construct()
Used to intercept the new operator
construct: function(target, argumentsList, newTarget) {}
Copy the code
target
: Target object.argumentsList
:constructor
Parameter list.newTarget
: The constructor that was originally called.
This method intercepts the following operations on the target object:
new proxy(... args)
Reflect.construct()
Note:
- In order to make the
new
Operator in the generatedProxy
Object, the target object used to initialize the proxy must itself have[[Construct]]
Internal methods, i.enew target
It has to be valid. For instancetarget
Is afunction
Constraints:
construct
Method must return an object or an error will be thrownTypeError
const p = new Proxy(function () {}, {
construct: function (target, argumentsList, newTarget) {
return 1; }});new p(); // 'construct' on proxy: trap returned non-object ('1')
Copy the code
handler.defineProperty()
Used to intercept the Object.defineProperty() operation
defineProperty: function(target, property, descriptor) {}
Copy the code
target
: Target object.property
: The name of the attribute whose description is to be retrieved.descriptor
: Descriptor of the property to be defined or modified.
Note:
defineProperty
The method must also return a Boolean value indicating whether the operation defining the property was successful. Return in strict modefalse
Flip aTypeError
)defineProperty
The method can only accept the following standard attributes, and the rest are not directly available (example code below) :enumerable
configurable
writable
value
get
set
var p = new Proxy({}, {
defineProperty(target, prop, descriptor) {
console.log(descriptor);
return Reflect.defineProperty(target, prop, descriptor); }});Object.defineProperty(p, 'name', {
value: 'proxy'.type: 'custom'
});
// { value: 'proxy' }
Copy the code
This method intercepts the following operations on the target object:
Object.defineProperty()
Reflect.defineProperty()
Constraints:
- If the target object is not extensible, attributes cannot be added.
const obj = {
a: 10
};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
defineProperty(target, prop, descriptor) {
return true; }});Object.defineProperty(p, 'name', {
value: 'proxy'
}); // 'defineProperty' on proxy: trap returned truish for adding property 'name' to the non-extensible proxy target
Copy the code
- You cannot add or modify a property to be unconfigurable if it does not exist as a non-configurable property of the target object.
const obj = {
a: 10
};
const p = new Proxy(obj, {
defineProperty(target, prop, descriptor) {
return true; }});Object.defineProperty(p, 'a', {
value: 'proxy'.configurable: false});// trap returned truish for defining non-configurable property 'a' which is either non-existant or configurable in the proxy target
Copy the code
- If the target object has a corresponding configurable property, that property may not be unconfigurable.
- If an attribute has a corresponding attribute in the target object, then
Object.defineProperty(target, prop, descriptor)
No exception will be thrown. - In strict mode,
false
As ahandler.defineProperty
Method return value will be thrownTypeError
The exception.
const obj = {
a: 10
};
const p = new Proxy(obj, {
defineProperty(target, prop, descriptor) {
return false}});Object.defineProperty(p, 'a', {
value: 'proxy'});// 'defineProperty' on proxy: trap returned falsish for property 'a'
Copy the code
handler.deleteProperty()
Used to intercept delete operations on object properties
deleteProperty: function(target, property) {}
Copy the code
target
: Target object.property
: Indicates the name of the attribute to be deleted.
Return value: A Boolean value must be returned indicating whether the property was successfully deleted. (This time return false will not error)
This method intercepts the following operations:
- Delete attributes:
delete proxy[foo]
和delete proxy.foo
Reflect.deleteProperty()
Constraints:
- If a property of the target object is not configurable, it cannot be deleted. And trying to delete will throw
TypeError
const obj = {};
Object.defineProperty(obj, 'a', {
value: 'proxy'});const p = new Proxy(obj, {
deleteProperty: function (target, prop) {
return true; }});delete p.a; // trap returned truish for property 'a' which is non-configurable in the proxy target
Copy the code
handler.getOwnPropertyDescriptor()
Used to intercept the getOwnPropertyDescriptor() method on object properties
getOwnPropertyDescriptor: function(target, prop) {}
Copy the code
target
: Target object.prop
: Attribute name.
Return value: An object or undefined must be returned.
This method intercepts the following operations:
Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
Constraints:
getOwnPropertyDescriptor
Must return oneobject
或undefined
const obj = { a: 10 };
const p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return ' '; }});Object.getOwnPropertyDescriptor(p, 'a'); // trap returned neither object nor undefined for property 'a'
Copy the code
- If a property exists as a non-configurable property of the target object, the property cannot be reported as nonexistent.
const obj = { a: 10 };
Object.defineProperty(obj, 'b', {
value: 20
});
const p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return undefined; }});Object.getOwnPropertyDescriptor(p, 'b'); // trap returned undefined for property 'b' which is non-configurable in the proxy target
Copy the code
- If a property exists as a property of the target object and the target object is not extensible, the property cannot be reported as nonexistent.
const obj = { a: 10 };
Object.preventExtensions(obj);
const p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return undefined; }});Object.getOwnPropertyDescriptor(p, 'a'); // trap returned undefined for property 'a' which exists in the non-extensible proxy target
Copy the code
- If an attribute does not exist as a target object, and the target object is not extensible, it cannot be reported as existing.
const obj = { a: 10 };
Object.preventExtensions(obj);
const p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return Object.getOwnPropertyDescriptor(obj, prop) || {}; }});console.log(Object.getOwnPropertyDescriptor(p, 'a'))
Object.getOwnPropertyDescriptor(p, 'b'); // trap returned descriptor for property 'b' that is incompatible with the existing property in the proxy target
Copy the code
- If a property exists as a property of the target object itself, or as a configurable property of the target object, it cannot be reported as unconfigurable
const obj = { a: 10 };
const p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return { configurable: false}; }});Object.getOwnPropertyDescriptor(p, 'a'); // trap reported non-configurability for property 'a' which is either non-existant or configurable in the proxy target
Copy the code
Object.getOwnPropertyDescriptor(target)
The results can be usedObject.defineProperty
Applies to the target object and does not throw an exception.
handler.getPrototypeOf()
A method used to intercept a stereotype that reads a proxy object
getPrototypeOf(target) {}
Copy the code
target
: Target object to be proxied.
Return value: must return an object value or null, cannot return a primitive value of another type.
This method intercepts the following operations:
Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
Examples are as follows:
const obj = {};
const p = new Proxy(obj, {
getPrototypeOf(target) {
return Array.prototype; }});console.log(
Object.getPrototypeOf(p) === Array.prototype, // true
Reflect.getPrototypeOf(p) === Array.prototype, // true
p.__proto__ === Array.prototype, // true
Array.prototype.isPrototypeOf(p), // true
p instanceof Array // true
);
Copy the code
Constraints:
getPrototypeOf()
Method returns neither an object nor an objectnull
。
const obj = {};
const p = new Proxy(obj, {
getPrototypeOf(target) {
return "foo"; }});Object.getPrototypeOf(p); // TypeError: trap returned neither object nor null
Copy the code
- The target object is not extensible, and
getPrototypeOf()
The prototype returned by the method is not the prototype of the target object itself.
const obj = {};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
getPrototypeOf(target) {
return{}; }});Object.getPrototypeOf(p); // proxy target is non-extensible but the trap did not return its actual prototype
Copy the code
handler.has()
Mainly used to intercept in and with operations
has: function(target, prop) {}
Copy the code
target
: Target objectprop
: Attributes that need to be checked for existence
Return value: Boolean (There is nothing wrong with returning a Boolean that can be converted to Boolean)
This method intercepts the following operations:
- Attribute query:
foo in proxy
- Inherited attribute query:
foo in Object.create(proxy)
with
Check:with(proxy) { (foo); }
Reflect.has()
Constraints:
- If a property of the target object itself cannot be configured, the property cannot be hidden by the proxy
const obj = {};
Object.defineProperty(obj, 'a', {
value: 10
})
const p = new Proxy(obj, {
has: function (target, prop) {
return false; }});'a' in p; // trap returned falsish for property 'a' which exists in the proxy target as non-configurable
Copy the code
- If the target object is an unextensible object, the properties of that object cannot be hidden by the proxy
const obj = { a: 10 };
Object.preventExtensions(obj);
const p = new Proxy(obj, {
has: function(target, prop) {
return false; }});'a' in p; // trap returned falsish for property 'a' but the proxy target is not extensible
Copy the code
handler.isExtensible()
Used to intercept the Object.isextensible () operation on an Object
isExtensible: function(target) {}
Copy the code
target
: Target object.
This method intercepts the following operations on the target object:
Object.isExtensible()
Reflect.isExtensible()
Return value: Boolean value or a value that can be converted to Boolean.
Constraints:
Object.isExtensible(proxy)
Must be the sameObject.isExtensible(target)
Returns the same value.- if
Object.isExtensible(target)
returnture
,Object.isExtensible(proxy)
Must be returnedtrue
Or fortrue
The value of the - if
Object.isExtensible(target)
returnfalse
,Object.isExtensible(proxy)
Must be returnedfalse
Or forfalse
The value of the
- if
const p = new Proxy({}, {
isExtensible: function(target) {
return false; }});Object.isExtensible(p); // trap result does not reflect extensibility of proxy target (which is 'true')
Copy the code
handler.ownKeys()
Used to intercept reflect.ownkeys ()
ownKeys: function(target) {}
Copy the code
target
: Target object
Return value: an enumerable object
This method intercepts the following operations on the target object (with some additional restrictions) :
Object.getOwnPropertyNames()
- Returns the property name of all the properties of the specified object (including non-enumerable properties but not those with the Symbol value as the name)
- You can only get the results that are returned
String
The,Symbol
Type will be ignored
Object.keys()
- Returns an array of the self-enumerable properties of a given object, the order and use of the property names in the array
for... in
The same order is returned as the loop iterates through the object. Enumerable properties can be passedfor... in
Loop through (unless the property name is a Symbol) - So you can only get enumerable results back
String
An array of
- Returns an array of the self-enumerable properties of a given object, the order and use of the property names in the array
Object.getOwnPropertySymbols()
- Returns only an array of all Symbol attributes for a given object itself
- You can only get the results that are returned
Symbol
,String
Type will be ignored
Reflect.ownKeys()
- Returns the property key of the target object itself
- Return everything
const mySymbel = Symbol('juanni');
const obj = { a: 10 };
Object.defineProperty(obj, 'b', {
configurable: false.enumerable: false.value: 10});Object.defineProperty(obj, mySymbel, {
configurable: true.enumerable: true.value: 10});const p = new Proxy(obj, {
ownKeys: function (target) {
return ['a'.'b', mySymbel]; }});console.log(Object.getOwnPropertySymbols(p)); // [Symbol(juanni)]
console.log(Object.getOwnPropertyNames(p)); // ["a", "b"]
console.log(Object.keys(p)); // ["a"]
console.log(Reflect.ownKeys(p)); // ["a", "b", Symbol(juanni)]
Copy the code
Constraints:
ownKeys
Must result in an array
const obj = {
a: 10
};
const p = new Proxy(obj, {
ownKeys: function (target) {
return 123; }});Object.getOwnPropertyNames(p); // CreateListFromArrayLike called on non-object
Copy the code
- The element type of the array is either one
String
Or oneSymbol
const obj = {
a: 10
};
const p = new Proxy(obj, {
ownKeys: function (target) {
return [123]; }});Object.getOwnPropertyNames(p); // 123 is not a valid property name
Copy the code
- The result list must contain all non-configurable (
non-configurable
), own (own
) properties ofkey
const obj = {
a: 10
};
Object.defineProperty(obj, 'b', {
configurable: false.enumerable: true.value: 10});const p = new Proxy(obj, {
ownKeys: function (target) {
return[]; }});Object.getOwnPropertyNames(p); // trap result did not include 'b'
Copy the code
- If the target object is not extensible, the result list must contain all of the target object’s own (
own
) properties ofkey
Can’t have any other value
const obj = { a: 10 };
Object.preventExtensions(obj);
const p = new Proxy(obj, {
ownKeys: function (target) {
return ['a'.'d']; }});Object.getOwnPropertyNames(p); // trap returned extra keys but proxy target is non-extensible
Copy the code
handler.preventExtensions()
Used to intercept the Object.preventExtensions() operation on the Object
preventExtensions: function(target) {}
Copy the code
target
: The target object to intercept
This method intercepts the following operations on the target object:
Object.preventExtensions()
Reflect.preventExtensions()
Return value: Boolean
Constraints:
- Only when the
Object.isExtensible(proxy)
是false
时Object.preventExtensions(proxy)
In order totrue
const p = new Proxy({}, {
preventExtensions: function (target) {
return true; }});Object.preventExtensions(p); // trap returned truish but the proxy target is extensible
Copy the code
handler.setPrototypeOf()
Used to intercept object.setPrototypeof () operations on objects
setPrototypeOf: function(target, prototype) {}
Copy the code
target
: Intercepted target objectprototype
: Object new prototype or fornull
This method intercepts the following operations on the target object:
Object.setPrototypeOf()
Reflect.setPrototypeOf()
Return value: Boolean
Constraints:
- if
target
Unextensible, prototype parameters must be matched withObject.getPrototypeOf(target)
The value of the phase
const obj = {
a: 10
};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
setPrototypeOf(target, prototype) {
Object.setPrototypeOf(target, prototype)
return true; }});Object.setPrototypeOf(obj, null); // #<Object> is not extensible
Copy the code
undoProxy
The proxy.revocable () method is used to create a revocable Proxy object. Such proxies can be revoked and closed by the REVOKE function. After the agent is closed, any operation on the agent causes TypeError
const revocable = Proxy.revocable({}, {
get: function (target, name) {
return "The [[" + name + "]]"; }});const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1 // Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // Cannot perform 'deleteProperty' on a proxy that has been revoked
typeof proxy // "object", typeof doesn't trigger any trap
Copy the code
Application in Mobx5
Here we intend to use Vue and Mobx to reflect the advantages of Proxy
Let’s start with vue2.x because of the restrictions imposed by using defineProperty:
- Cannot detect using index to set an item directly:
vm.items[indexOfItem] = newValue
- Cannot detect the length of the modified array:
vm.items.length = newLength
- Cannot detect addition or deletion of object attributes
- .
Next door Mobx4 also uses defineProperty, but uses a series of hacks to get around some restrictions:
- useClass array object by itselfTo solve the above problems 1 and 2. But there are additional restrictions:
Array.isArray
returnfalse
- Must be used when passed outside or when you need to use a real array
array.slice()
Create a shallow copy of the real array sort
和reverse
Does not change the array itself, but simply returns a sorted/reversed copy
- Failed to resolve the issue of not being able to add/remove attributes directly on objects
Because an array-like object is used, length becomes an attribute on the object rather than the length of the array, and can therefore be hijacked. For more tips, check out Observablearray.ts
Object.defineProperty(ObservableArray.prototype, "length", {
enumerable: false.configurable: true.get: function() :number {
return this.$mobx.getArrayLength()
},
set: function(newLength: number) {
this.$mobx.setArrayLength(newLength)
}
})
Copy the code
Mobx5, which was released this year using a Prxoy rewrite, successfully addresses these issues. Here’s a quick look at how it works:
- Intercepting the operation to modify array length/directly set the value:
const arrayTraps = {
get(target, name) {
if (name === $mobx) return target[$mobx]
// Successfully intercepted length
if (name === "length") return target[$mobx].getArrayLength()
if (typeof name === "number") {
return arrayExtensions.get.call(target, name)
}
if (typeof name === "string"&&!isNaN(name as any)) {
return arrayExtensions.get.call(target, parseInt(name))
}
if (arrayExtensions.hasOwnProperty(name)) {
return arrayExtensions[name]
}
return target[name]
},
set(target, name, value): boolean {
// Successfully intercepted length
if (name === "length") {
target[$mobx].setArrayLength(value)
return true
}
// Set the array value directly
if (typeof name === "number") {
arrayExtensions.set.call(target, name, value)
return true
}
// Set the array value directly
if (!isNaN(name)) {
arrayExtensions.set.call(target, parseInt(name), value)
return true
}
return false
},
preventExtensions(target) {
fail(`Observable arrays cannot be frozen`)
return false}}Copy the code
- Add/remove attributes directly to an object
- This is essentially because
defineProperty
Is caused by a property on a hijacked object, there is no way to hijack a property that does not exist on an object, andPrxoy
Hijacking the entire object eliminates this problem
- This is essentially because
polyfill
Polyfill isn’t easy because of language limitations, but some implementations are ok:
Proxy-polyfill is a Google DefineProperty-based app that supports only Get, set, apply, construct and Revocable. The code is only 100 lines long and very simple, so I won’t go into details
The practical application
Now that we know the basics, it’s time to put them into practice
Design patterns
There happens to be a design pattern called the proxy pattern: provide a proxy for other objects to control access to that object. In some cases, an object is inappropriate or cannot directly reference another object, and a proxy object can act as an intermediary between the client and the target object.
There are two advantages:
- Single responsibility principle: Object-oriented design encourages the distribution of different responsibilities into fine-grained objects,
Proxy
Based on the original object, the function is derived without affecting the original object, which conforms to the design concept of loose coupling and high cohesion. - Open-closed principle: Proxies can be removed from the program at any time without changing other parts of the code. In a real world scenario, proxies may no longer be needed for a variety of reasons as the version iterations, so proxy objects can be easily replaced with calls to the original object
Be aware ofthis
There is one small point to note before warming up – this:
const target = {
foo() {
return {
thisIsTarget: this === target,
thisIsProxy: this=== proxy, }; }};const handler = {};
const proxy = new Proxy(target, handler);
console.log(target.foo()); // {thisIsTarget: true, thisIsProxy: false}
console.log(proxy.foo()); // {thisIsTarget: false, thisIsProxy: true}
Copy the code
Normally, using this in Proxy to call a method or get/set a property is fine, because it will eventually be intercepted on the original object, but if you use this or some built-in method that requires this to point correctly, you need to be careful. Okay
- right
this
Using the SAO operation requires extra care
const _name = new WeakMap(a);class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this); }}const juanni = new Person('Juanni');
const proxy = new Proxy(juanni, {});
console.log(juanni.name); // 'juanni'
console.log(proxy.name); // undefined
Copy the code
- Built-in method dependencies
this
const target = new Date(a);const handler = {};
const proxy = new Proxy(target, handler);
// An error is reported depending on this
proxy.getDate(); // this is not a Date object.
// Modify the scheme
const handler = {
get(target, propKey, receiver) {
if (propKey === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, propKey, receiver); }};const proxy = new Proxy(new Date('2020-12-24'), handler);
proxy.getDate(); / / 24
Copy the code
Warm up
Let’s warm up a bit and look at a simple one: suppose we have a function tracePropAccess(obj, propKeys). Whenever we set or obtain obj’s property in the propKeys, it will be logged.
Since this is a simple warm-up demo, I’ll go straight to the code done using defineProperty and Proxy for comparison
// ES5
function tracePropAccess(obj, propKeys) {
const propData = Object.create(null);
propKeys.forEach(function (propKey) {
propData[propKey] = obj[propKey];
Object.defineProperty(obj, propKey, {
get: function () {
console.log(`GET ${propKey}`);
return propData[propKey];
},
set: function (value) {
console.log(`SET ${propKey} = ${value}`); propData[propKey] = value; }}); });return obj;
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `Point( The ${this.x} , The ${this.y}) `;
}
}
p = tracePropAccess(new Point(7),'x'.'y']);
p.x // GET x
p.x = Awesome! // SET x = 666
p.toString()
// GET x
// GET y
Copy the code
// ES6 with Proxy
function tracePropAccess(obj, propKeys) {
const propKeySet = new Set(propKeys);
return new Proxy(obj, {
get(target, propKey, receiver) {
if (propKeySet.has(propKey)) {
console.log(`GET ${propKey}`);
}
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
if (propKeySet.has(propKey)) {
console.log(`SET ${propKey} = ${value}`);
}
return Reflect.set(target, propKey, value, receiver); }}); }class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `Point( The ${this.x} , The ${this.y}) `;
}
}
p = tracePropAccess(new Point(7),'x'.'y']);
p.x // GET x
p.x = Awesome! // SET x = 666
p.toString()
// GET x
// GET y
Copy the code
Negative group index
The NTH element of an array can be accessed using a negative index. Now we have a new way to implement this feature directly:
function createArray(array) {
if(!Array.isArray(array)) {
throw Error('must be an array');
}
const handler = {
get(target, propKey, receiver) {
const index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver); }};return new Proxy(array, handler);
}
const arr = createArray(['a'.'b'.'c']);
console.log(arr[- 1]); // c
Copy the code
Intercept calls
No single operation can be intercepted on a method call, because the method call is treated as two separate operations: the get retrieval function is used first, and then the function is called.
const obj = {
multiply(x, y) {
return x * y;
},
squared(x) {
return this.multiply(x, x); }};function traceMethodCalls(obj) {
const handler = {
get(target, propKey, receiver) {
const origMethod = target[propKey];
return function (. args) {
const result = origMethod.apply(this, args);
console.log(propKey + JSON.stringify(args)
+ '- >' + JSON.stringify(result));
returnresult; }; }};return new Proxy(obj, handler);
}
const tracedObj = traceMethodCalls(obj);
console.log(tracedObj.multiply(2.7));
/ / multiply - > 14 [2, 7]
// test.js:25 14
console.log(tracedObj.squared(9));
/ / multiply [9, 9] - > 81
// test.js:16 squared[9] -> 81
// test.js:26 81
Copy the code
We can see that method calls (such as this.multiply(x, x)) can be intercepted even if this points to the Proxy inside the original object
The singleton pattern
function singleton(func) {
let instance,
handler = {
construct: function (target, args) {
if(! instance) { instance =new func();
}
returninstance; }};return new Proxy(func, handler);
}
Copy the code
Make a property disappear completely
Reflect.has
、Object.hasOwnProperty
、Object.prototype.hasOwnProperty
、in
All operators are used[[HasProperty]]
, can be accessed throughhas
Intercept.Object.keys
、Object.getOwnPropertyNames
,Object.entrie
use[[OwnPropertyKeys]]
, can be accessed throughownKeys
Intercept.Object.getOwnPropertyDescriptor
Using the[[GetOwnProperty]]
Can be achieved bygetOwnPropertyDescriptor
Intercept.
So we can write code like this to make a property disappear completely
function hideProperty(object, ... propertiesToHide) {
const proxyObject = new Proxy(object, {
has(object, property) {
if(propertiesToHide.indexOf(property) ! =- 1) {
return false;
}
return Reflect.has(object, property);
},
ownKeys(object) {
return Reflect.ownKeys(object).filter(
(property) = > propertiesToHide.indexOf(property) == - 1
);
},
getOwnPropertyDescriptor(object, property) {
if(propertiesToHide.indexOf(property) ! =- 1) {
return undefined;
}
return Reflect.getOwnPropertyDescriptor(object, property); }});return proxyObject;
}
Copy the code
other
There are many other things you can do with a Proxy, such as:
- Type checking
- Peel check logic
- Private variables
- Through the use of
has
.ownKeys
,getOwnPropertyDescriptor
和get
.set
To make the property private
- Through the use of
- Data binding
- The caching proxy
- Verify the agent
- Lazy loading of images
- Merge request
- For more interesting work, see awese-ES2015-Proxy
reference
- Proxy
- Metaprogramming in ES6: Part 2 — Reflect
- Metaprogramming in ES6: Proxies
- ECMAScript ® 2015 Language Specification
- Metaprogramming with proxies
- Sorry, Proxy learning can really do whatever it wants