The pre-alpha version of VUe3.0 is now open source. As the author previously announced, the data response part has been implemented by ES6 Proxy instead of Object.defineProperty. If you are interested, you can see the implementation source. Vue is starting to use proxies for data responsiveness, so it’s worth taking some time to learn about proxies.

Object. DefineProperty defects

Object. DefineProperty () : Object. DefineProperty () : Object.

The getter/setter method of the object property listens for changes to the data, and the getter is also used for dependency collection, and the setter notifies the subscriber to update the view when the data changes.

The code looks something like this:

function defineReactive(obj, key, value) { Object.defineProperty(obj, key, { enumerable: true, configurable: True, get() {collectDeps() // Collect dependencies return value}, set(newVal) {observe(newVal); // If the object needs recursive child property if (newVal! == value) {notifyRender() // notifyRender() value = newVal; } } }) } function observe(obj) { if (! obj || typeof obj! === 'object') { return } Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]); }) } var data = { name: 'wonyun', sex: 'male' } observe(data)Copy the code

Although Object.defineProperty can accomplish data responsiveness by setting getters/setters for attributes, it is not a perfect solution to achieve data responsiveness. In some cases, it needs to be patched or hacked, which is also its weakness, mainly in two aspects:

  1. Unable to detect additions or deletions of object properties

    Due to the dynamic nature of JS, new attributes can be added to the Object or one of them can be deleted. In this case, for responsive objects established by object.defineProperty method, only the existing data of the Object can be traced, but new attributes and deleted attributes cannot be traced, which needs to be handled separately.

    Currently, Vue ensures that new attributes of responsive objects are also responsive in two ways:

    • Vue.set(obj, propertName/index, value)

    • New attributes for children of reactive objects that can be reassigned

      data.location = { x: 100, y: 100 } data.location = {... data, z: 100}Copy the code

    Delete (obj, propertyName/index) or Vue. $delete(obj, propertyName/index). Similar to deleting an attribute of a child of a reactive object, you can also reassign a value to the child.

  2. Cannot listen for array changes

    Vue in the implementation of the array response type, it USES some hack, can’t be monitored array by rewriting the condition of the array part ways to implement reactive, that will only limit in the array of push/pop/shift/unshift/splice/sort/reverse seven methods, Other array methods and array uses are not detected, such as the following two uses:

    • vm.items[index] = newValue

    • vm.items.length–

    Array.prototype (Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array)

    const methods = ['pop','shift','unshift','sort','reverse','splice', 'push']; Prototype let proto = object.create (array.prototype); // Copy array.prototype and point it to array.prototype. Prototype [method]. Call (this,... arguments); Function observe(obj) {if (array.isarray (obj)) {// Array obj.__proto__ = proto; // Change the array prototype return; } if (typeof obj === 'object') { ... // Responsive implementation of objects}}Copy the code

The use of Proxy

Proxy, literally means acting, is to provide a new API ES6, modify the default behavior of the certain operations, can be understood as the target object before doing a layer of interception, external intercept all access must be through the layer, through this layer intercept can do many things, such as the data filtering, modify or collect information. Borrow a skillfully used picture of proxy, which vividly expresses the function of proxy.

The Proxy constructor provided natively in ES6 is used as follows:

var proxy = new Proxy(obj, handler)Copy the code

Where obj is the object to be intercepted by Proxy, handler is used to customize the interception operation, and returns a new Proxy object Proxy. Proxy Features:

  • Proxy directly represents the entire object rather than object attributes

    Proxy’s Proxy is for the whole Object, not for a property like Object.defineProperty. A single layer of proxying allows you to listen for all property changes under the peer structure, including new and deleted properties

  • Proxy can also listen for array changes

    For example, the Object. DefineProperty implementation used by vue above in a reactive way is relatively simple to implement with a Proxy:

    let handler = { get(target, key){ if (target[key] === 'object' && target[key]! Return new Proxy(target[key], hanlder)} collectDeps() return reflect.get (target, hanlder) key) }, set(target, key, Value) {if (key === 'length') return true notifyRender() return reflect.set (target, key, value); } } let proxy = new Proxy(data, handler); Let proxy1 = new proxy ({arr: []}, handler); Proxy1.arr [0] = 'proxy' // Supports array content changesCopy the code

    Get /set in the Proxy constructor above is two of the 13 traps defined by Proxy, which has 13 Proxy operations:

    trap describe
    handler.get Intercepts when getting properties of an object
    handler.set Intercepts when setting the properties of an object
    handler.has interceptpropName in proxy“, returns Boolean
    handler.apply Intercepting the actions of proxy instances as function calls,proxy(args),proxy.call(...),proxy.apply(..)
    handler.construct Intercepts the operations that the proxy calls as constructors
    handler.ownKeys Intercepts operations to obtain proxy instance attributes, includingObject.getOwnPropertyNames,Object.getOwnPropertySymbols,Object.keys,for... in
    handler.deleteProperty interceptdelete proxy[propName]operation
    handler.defineProperty interceptObjecet.defineProperty
    handler.isExtensible interceptObject.isExtensibleoperation
    handler.preventExtensions interceptObject.preventExtensionsoperation
    handler.getPrototypeOf interceptObject.getPrototypeOfoperation
    handler.setPrototypeOf interceptObject.setPrototypeOfoperation
    handler.getOwnPropertyDescriptor interceptObject.getOwnPropertyDescriptoroperation

Proxy Proxy target objects, which are done by operating on the 13 traps above, correspond to the 13 static methods provided by another apiReflect in ES6. The two methods are used together. When modifying a proxy object, you also need to synchronize it to the target object of the proxy. This synchronization is done using the Reflect corresponding method. For example reflect.set (target, key, value) synchronizes changes to the target object properties. One thing to add:

Among the 13 trap operation methods, if the handler does not set the trap operation method during initialization, it directly operates on the target object and does not intercept the trap operation

Proxy usage scenarios

The Proxy uses a layer of interception before the target object, so all external access to the target object must pass through this interception. So through this layer of interception, you can do a lot of things, such as control filtering, caching, data verification and so on. It can be said that Proxy is used in a wide range of scenarios. Here are a few scenarios, and more practical scenarios can refer to Proxy skillfully.

  • Vue3 data response

    Vue3 uses Proxy to intercept data reading and setting, and implement data dependency collection and trigger view update operation in intercept trap. The main pseudo-codes of VUe3 are as follows:

    Function get(target, key, receiver) {// handler.get const res = reflect. get(target, key, receiver) receiver) if(isSymbol(key) && builtInSymbols.has(key)) return res if (isRef(res)) return res.value track(target, Operationtypes.get, key) // Collection depends on return isObject(res)? reactive(res) : Function set(target, key, value) Receiver) {value = toRaw(value) // Get cache response data oldValue = target[key] if (isRef(oldValue) &&! isRef(value)) { oldValue.value = value return true } const result = Reflect.set(target, key, value, Receiver) if (target === toRaw(receiver)) {// Set intercepts only the object itself... {return result}} {return result}} {return result}Copy the code
  • Gets the value of the property, returns the default value if there is no property or if the property is empty

    Get (object, path, [defaultValue]) in the loadsh library, if the value of some optional fields is empty or does not exist, you can set a defaultValue. The following object form _. Get with Proxy implementation, code is as follows:

    function getValueByPath(object, path, defaultValue) { let proxy = new Proxy(object, { get(target, key) { if (key.startsWith('.')) { key = key.slice(1); } if (key.includes('.')) { path = path.split('.'); let index = 0, len = path.length; while(target ! = null && index < len) { target = target[path[index++]] } return target || defaultValue; } if (! (key in target) || ! target[key]) { return defaultValue } return Reflect.get(target, key) } }); return proxy[path] }Copy the code

    If path has a nested path like A.B.C, it is handled directly in Proxy handler.get. If you call a proxy object instance such as proxy.a.b.c, you need to create a proxy instance in proxy handler.get to return the attributes of the object, similar to the following:

    function getValueByPath(object, path, defaultValue) { return proxy = new Proxy(object, { get(target, key) { if (isObject(target[key])){ return new Proxy(target[key], {get(){}}) } ... // other ellipses}})}Copy the code
  • Realize the access of array negative index

    Substr (‘ index ‘, ‘index’, ‘index’, ‘index’, ‘index’);

    function getArrItem(arr) {
        return new Proxy(arr, {
            get(target, key, receiver) {
                let index = Number(key);
                if (index < 0) {
                  key = String(target.length + index);
                }
                return Reflect.get(target, key, receiver)
            }
        });
    }Copy the code

The Proxy disadvantage

Although Proxy has many advantages over Object.defineProperty, it does not mean that Proxy does not have disadvantages, which are mainly reflected in the following two aspects:

  • Compatibility problem, no complete polyfill

    Proxy is a newly released API of ES6, and the browser’s support for Proxy can be found in Caniuse, as shown in the following figure:

    It can be seen that most browsers support Proxy, but some browsers or their earlier versions do not support Proxy. Internet Explorer, QQ browser, and Baidu browser do not support Proxy at all. Therefore, Proxy has compatibility problems. Is there a polyfill solution like the other ES6 features? The answer is not so optimistic. Babel, one of the leading ES6 converters, explains clearly on its website:

    Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.

    In other words, due to the limitations of ES5, ES6 Proxy cannot be fully polyfilled, so Babel does not provide corresponding conversion support. The implementation of Proxy requires JS engine level support, which is provided by most major JS engines at present. You can view ES6 Proxy Compatibilit.

    However, as of October 2019, Proxy Polyfill developed by Google: proxy-Polyfill, its implementation is also incomplete, as shown in:

    • Only four traps of Proxy are supported: Get, set, Apply, and Construct

    • Partially supported trap functions are also incomplete. For example, set does not support new attributes

    • The Polyfill cannot proxy an array

  • Performance issues

    Another is the performance of the Proxy problem, for this someone specially made a contrast experiment, the original thoughts here – on – es6 – proxies – performance, the corresponding Chinese translation can reference es6 Proxy performance I’ve seen. Proxy performance is worse than Promise, which requires a trade-off between performance and simplicity. Vue3, for example, uses Proxy to intercept objects and arrays easily to be responsive to data, especially arrays.

    In addition, Proxy as a new standard will be the focus of browser vendors continued performance optimization, performance is believed to be gradually improved.

reference

  • The proper use of the Proxy

  • thoughts-on-es6-proxies-performance

  • My opinion on ES6 Proxy performance

  • Interviewer: How better is implementing a two-way binding Proxy than defineProperty?

  • es6-proxy-polyfill-for-ie11