The Proxy object corresponding to the Proxy API is a native object introduced in ES2015 that defines the custom behavior of basic operations (such as attribute lookup, assignment, enumeration, function call, etc.).

Literally, a Proxy object is a Proxy for a target object, through which any operation on the target object (instantiation, adding/deleting/modifying properties, and so on) must pass. So we can block and filter or modify all operations from the outside world.

These proxy-based features are often used to:

  • Create a “reactive” object, such as the Reactive method in Vue3.0.
  • Create an isolated JavaScript “sandbox.”

Proxy common usage

The Proxy grammar:

const p = new Proxy(target, handler)
Copy the code
  • Target: The target object (which can be any type of object, including a native array, a function, or even another Proxy) to be wrapped with a Proxy.
  • Handler: An object with functions as attributes that define the behavior of agent P when performing various operations.

For example, the following is a very simple use:

let foo = {
	a: 1.b: 2
}
let handler = {
    get:(obj,key) = >{
        console.log('get')
        return key in obj ? obj[key] : undefined}}let p = new Proxy(foo,handler)
console.log(p.a) / / 1
Copy the code

In the code above, P is a proxy object for Foo, and all operations on p are synchronized to Foo.

Proxy.revocable() also provides another way to generate Proxy objects:

const { proxy,revoke } = Proxy.revocable(target, handler)
Copy the code

The return value of this method is an object of the structure {“proxy”: proxy, “REVOKE “: REVOKE}, where:

  • Proxy: represents the newly generated proxy object itself, and in the general waynew Proxy(target, handler)The created proxy object is no different, except that it can be undone.
  • Revoke: Revoke method that can be called to revoke the proxy object it was created with without any arguments.

Such as:

let foo = {
	a: 1.b: 2
}
let handler = {
    get:(obj,key) = >{
        console.log('get')
        return key in obj ? obj[key] : undefined}}let { proxy,revoke } = Proxy.revocable(foo,handler)

console.log(proxy.a) / / 1

revoke()

console.log(proxy.a) // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
Copy the code

Note that once a proxy object is destroyed, it becomes almost completely uncallable, and any proxiable operation performed on it throws TypeError exceptions.

The Proxy of the handler

In the above code, we only use the get handler, which is used when trying to get a property of an object. In addition, the Proxy has nearly 14 handlers, which can also be called hooks. They are:

Handler.getprototypeof () : This operation is triggered when the prototype of a proxy Object is read, such as when Object.getProtoTypeof (proxy) is executed. Handler.setprototypeof () : This action is triggered when the prototype of a proxy Object is set, such as when Object.setPrototypeof (proxy, null) is executed. Handler.isextensible () : this operation is triggered when determining whether a proxy Object isExtensible, such as when object.isextensible (proxy) is executed. Handler. PreventExtensions () : when he makes a proxy Object cannot extend trigger the operation, such as the execution Object. PreventExtensions (proxy). Handler. GetOwnPropertyDescriptor () : in acquiring a proxy Object a property description is triggered when the operation, such as the execution Object. GetOwnPropertyDescriptor (proxy,"foo"). Handler.defineproperty () : This action is triggered when the property description of a proxy Object is defined, such as when object.defineProperty (proxy,"foo", {}). Handler.has () : This action is triggered when determining whether the proxy object has a property, such as during execution"foo" inWhen the proxy. Handler.get () : Triggered when reading a property of a proxy object, such as when proxy.foo is executed. Handler.set () : Triggered when assigning a value to an attribute of a proxy object, such as proxy.foo = 1. Handler.deleteproperty () : This operation is triggered when an attribute of the proxy object is deleted, using the DELETE operator, such as when delete proxy.foo is executed. Handler. OwnKeys () : when the execution Object. GetOwnPropertyNames (proxy) and Object. GetOwnPropertySymbols triggered (proxy). Handler.apply () : When the proxy object is onefunctionFunction, when the apply() method is called, such as proxy.apply(). Handler.construct () : Construct when the proxy object is afunctionFunction when instantiated with the new keyword, such as new proxy().Copy the code

With these handlers, we can implement restrictive operations on objects, such as:

  • Do not delete or modify an attribute of an object
let foo = {
    a:1.b:2
}
let handler = {
    set:(obj,key,value,receiver) = >{
        console.log('set')
        if (key == 'a') throw new Error('can not change property:'+key)
        obj[key] = value
        return true
    },
    deleteProperty:(obj,key) = >{
        console.log('delete')
        if (key == 'a') throw new Error('can not delete property:'+key)
        delete obj[key]
        return true}}let p = new Proxy(foo,handler)

p.a = 3 // Uncaught Error

delete p.a  // Uncaught Error
Copy the code

Where, the receiver of the set method is usually a Proxy, that is, P, but when there is a code executing obj. Name = “jen”, obj is not a Proxy and does not contain the name attribute itself, but there is a Proxy on its prototype chain, then, The set method in the proxy handler is called, and obj is passed in as receiver.

  • Validates property changes
let foo = {
    a:1.b:2
}
let handler = {
    set:(obj,key,value) = >{
        console.log('set')
        if (typeof(value) ! = ='number') throw new Error('can not change property:'+key)
        obj[key] = value
        return true}}let p = new Proxy(foo,handler)

p.a = 'hello' // Uncaught Error
Copy the code

Proxy and reactive objects

Reactive objects in Vue3:

import {ref,reactive} from 'vue'. setup(){const name = ref('test')
  const state = reactive({
    list: []})return {
      name,
      state
  }
}
...
Copy the code

In Vue3, composition-API provides a way to create responsive objects, and reactive is internally implemented by Proxy API. In particular, the set method of handler can realize the logic related to two-way data binding. This is a big change to object.defineProperty () in vue2.x.

  • Object.defineproperty () can only listen for changes or changes to existing attributes and cannot detect additions or deletions of Object attributes, whereas Proxy can be easily implemented.

  • Object.defineproperty () cannot listen for property values to be array type changes, whereas Proxy can easily do this.

For example, listen for array changes:

let arr = [1]
let handler = {
    set:(obj,key,value) = >{
        console.log('set')
        return Reflect.set(obj, key, value); }}let p = new Proxy(arr,handler)
p.push(2)
Copy the code

Reflect.set() is used to change the value of an array, which we can refer to as reflect.set ().

let foo = {
    a:1.b:2
}
let handler = {
    set:(obj,key,value) = >{
        console.log('set')
        // Bidirectional binding related logic
        obj[key] = value
        return true}}let p = new Proxy(foo,handler)

p.a = 3
Copy the code

In the above code, the simple object foo is perfectly fine, but if Foo is a complex object with many nested objects, then the set method will not fire when trying to change the inner object value. To solve this situation, Vue3 uses a recursive approach to solve this problem:

let foo = {a: {c:3.d: {e:4}},b:2}
const isObject = (val) = >{
    returnval ! = =null && typeof val === 'object'
}
const createProxy = (target) = >{
    let p = new Proxy(target,{
        get:(obj,key) = >{
            let res = obj[key] ? obj[key] : undefined

            // Determine the type to avoid endless loops
            if (isObject(res)) {
                return createProxy(res)
            } else {
                return res
            }
        },
        set: (obj, key, value) = > {
          console.log('set') obj[key] = value; }})return p
}

let result = createProxy(foo)

result.a.d.e = 6 // Print out set
Copy the code

When trying to modify the properties of a multi-layer nested object, it will trigger the get method of the object at the upper level of the property, which can be used to add a Proxy Proxy to the object at each level, thus realizing the property modification problem of the multi-layer nested object.

Of course, the above code is only a snapshot of Reactive in Vue3, and more details can be found in the source code.

As of now, Porxy API is a standard introduced from ES2015, and polyfill is not perfect in the industry. Therefore, compatibility issues should be carefully considered when using this API-related framework.