Understand why Vue3 uses Proxy

Posted by astronaut on Sun, 05 Sep 2021 07:53:15 +0200

preface

In Vue 3.0, Proxy replaces Object.defineProperty. What is Proxy, what can Proxy do, what is the difference between Object.defineProperty and Proxy in Vue, and why it should be replaced

What is Proxy?

Proxy objects are used to define custom behaviors of basic operations (such as attribute lookup, assignment, enumeration, function call, etc.).

Generally speaking, Proxy is an interceptor of object operation, which intercepts the operation of the target object and performs some custom behaviors.

How does Proxy work?

let p = new Proxy(target, handler);

Target the target object wrapped in Proxy (can be any type of object, including a native array, a function, or even another Proxy).

handler an object whose properties are functions that define the behavior of an agent when an operation is performed.

[for example]

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35; 
  }
});

let obj = Object.create(proxy);
obj.time // 35

No action forwarding agent

The proxy object p will forward all operations applied to it to the target object target. You can directly operate on the proxy object, and the operations will be forwarded to the target object.

[for example]

let target = {};
let p = new Proxy(target, {});

p.a = 37;   // Forward operation to destination

console.log(target.a);    // 37. The operation has been forwarded correctly

Operations that can be intercepted

There are 13 kinds of proxy operations. The code (property name / method name) of each operation and the way to trigger this operation are listed below. Note that if an operation is not defined, it will be forwarded to the target object.

handler.getPrototypeOf()

This operation is triggered when reading the prototype of the proxy object, such as when executing Object.getPrototypeOf(proxy).

handler.setPrototypeOf()

This operation is triggered when setting the prototype of the proxy object, such as when executing Object.setPrototypeOf(proxy, null).

handler.isExtensible()

This operation is triggered when determining whether a proxy object is extensible, such as when executing Object.isExtensible(proxy).

handler.preventExtensions()

This operation is triggered when a proxy object is made non extensible, such as when executing Object.preventExtensions(proxy).

handler.getOwnPropertyDescriptor()

This operation is triggered when obtaining the property description of a property of the proxy object, such as when executing Object.getOwnPropertyDescriptor(proxy, "foo").

handler.defineProperty()

This operation is triggered when defining the property description of a property of the proxy object, such as when executing Object.defineProperty(proxy, "foo", {}).

handler.has()

This operation is triggered when determining whether the proxy object has a property, such as when executing "foo" in proxy.

handler.get()

This operation is triggered when a property of the proxy object is read, such as when proxy.foo is executed

handler.set()

This operation is triggered when assigning a value to a property of the proxy object, such as when proxy.foo = 1 is executed.

handler.deleteProperty()

This operation is triggered when a property of the proxy object is deleted, such as when delete proxy.foo is executed.

handler.ownKeys()

This operation is triggered when all the property keys of the proxy object are obtained, such as when executing Object.getOwnPropertyNames(proxy).

handler.apply()

Triggered when the target object is a function and is called.

handler.construct()

This operation is triggered when constructing an instance of a proxy object whose target object is a constructor, such as when executing new proxy().

The direction of this

Special attention should be paid to this pointing in proxy objects and interception operations

Once the object is proxied, his this points to the proxy object

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

In the interception operation defined by the handler, this points to the handler and the receiver points to the proxy

const target = {
	m: 100
};
const handler = {
	get(target, property,receiver){
		console.log(this === handler)
		console.log(receiver === proxy)
		return target[property]
	}
};

const proxy = new Proxy(target, handler);
console.log(proxy.m)

What can Proxy do?

What can Proxy do with various commonly used interceptions? There are four commonly used operations

get()

The get method is used to intercept the read operation of a property. It can accept three parameters, namely, the target object, the property name and the proxy instance itself. All property calls will enter the same get, even if there are no properties.

  • You can create properties that you don't have
  • Data can be verified and converted when fetching data
  • You can customize some syntax operations
  • If get returns a function, it can convert a property into a method

[for example]

var base = {
	a:100,
	small:"hello world!!"
}

var proxy = new Proxy(base,{
	get(target, property,receiver){
		//Property to function
		if("fn" === property ){
			return function(value){
	      		console.log(value)
	      	}
		}
		
		if("ghost" === property ){
			return "ghost"
		}
		
		
		
		if("fn" === property ){
			return function(value){
	      		console.log(value)
	      	}
		}
		//Custom syntax sugar
		if(property.includes("_")){
			const direct = property.split("_")[1]
			const propertyBase = property.split("_")[0]
			switch (direct){
				case "Big":
					return receiver[propertyBase].toLocaleUpperCase()
				default:
					break;
			}
		}
		
		if(!(property in target)){
			throw new ReferenceError("Property \"" + property + "\" does not exist.");//Validate attribute values
		}
		
		return target[property]

	}
})

console.log(proxy.a)//Output 100 normal access
proxy.fn("fn")//Convert output fn attribute to method
console.log(proxy.small_Big)//Output HELLO WORLD!! Custom syntax sugar
console.log(proxy.ghost)//Output ghost create attribute
console.log(proxy.ghost_Big)//Use of output ghost receiver
console.log(proxy.b)//Error thrown during data validation

set()

The set method is used to intercept the assignment operation of an attribute. It can accept four parameters, namely, the target object, attribute name, attribute value and Proxy instance itself. The last parameter is optional.

  • It can be used to verify whether the attribute meets the requirements
  • Can be used to change the data format
  • Can be used to listen for data change events
  • Mask some assignment operations, such as "" Private variable at the beginning

[for example]

var base = {
	a:100,
	small:"hello world!!"
}


const A_Change_Event="aChangeEvent";
window.addEventListener(A_Change_Event,(e)=>{
	console.log(e.data.value)
})

var proxy = new Proxy(base,{
	set(obj, property, value,receiver){

		if("a" === property ){
			if (!Number.isInteger(value)) { 
				throw new TypeError('The'+property+'is not an integer');
			} 
			if (value > 100){ 
				throw new RangeError('The '+property+' seems invalid'); 
			}
			
			
			obj[property] = value;
			
			//event
			const dataChangeEvent = new Event(A_Change_Event);//Create an event
			dataChangeEvent.data = {value}
			window.dispatchEvent(dataChangeEvent)
			
		}
		
	}
})
proxy.a = 80;
console.log(proxy.a)
proxy.a = 120;

apply()

The function object can also be the target object of the proxy

The apply method intercepts function calls, calls, and apply operations.

The apply method can accept three parameters: the target object, the context object (this) of the target object, and the parameter array of the target object.

[for example]

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p()
// "I am the proxy"

construct ()

The new operation is intercepted by construct

Accept three parameters

  • Target: target object
  • args: parameter object of constructor
  • newTarget: the constructor used by the new command when creating an instance object

Proxy and Object.defineProperty in Vue

Object.defineProperty implements observe

Recursively traverse all properties, and use Object.defineProperty to define listening one by one

var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // Hahaha, the listening value has changed, King -- > DMQ

function observe(data) {
    if (!data || typeof data !== 'object') {
        return;
    }
    // Take out all attribute traversal
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    });
};

function defineReactive(data, key, val) {
    observe(val); // Listening sub attribute
    Object.defineProperty(data, key, {
        enumerable: true, // enumerable 
        configurable: false, // Can no longer define
        get: function() {
            return val;
        },
        set: function(newVal) {
            console.log('Hahaha, the monitoring value has changed ', val, ' --> ', newVal);
            val = newVal;
        }
    });
}

Proxy implements observe

There is no need to traverse the binding one by one in advance. Judge by key every time

observe(data) {
    const that = this;
    let handler = {
    get(target, property) {
        return target[property];
    },
    set(target, key, value) {
        let res = Reflect.set(target, key, value);
        that.subscribe[key].map(item => {
          item.update();
        });
        return res;
      }
    }
    this.$data = new Proxy(data, handler);
  }

Why use Proxy instead of Object.defineProperty?

vue3.0 uses Proxy instead of traversing the object, and uses the Object.defineProperty method to add set and get accessors to the property. In other words, you don't need to traverse, but directly monitor the data object.

Topics: Vue