Data hijacking and data broker

Keywords: Javascript Vue Attribute

Data hijacking

Data hijacking uses Object.defineProperty() to implement the two-way binding of vue. Let's see how it works.

let obj = {}, txt = ''
Object.defineProperty(obj,'txt',{
    set(val) {
    
        console.log('set ....')
        txt = val || '';
    },
    get() {
        //get callback will be triggered when getting obj.txt.
        console.log('get ....')
        return txt
    }
})

Disadvantages of Object.defineProperty
1. Unable to monitor the change of array
For instance

//When the monitored property is an array
let arr = [1,2,3]
let obj = {}
Object.defineProperty(obj,'arr',{
    set(val) {
        console.log('set',val)
        arr = val
    },
    get() {
        console.log('get')
        return arr
    }
})
obj.arr.push(4) // Get actually changes the value of arr, but instead of executing set, it executes get.
obj.arr = [1,2,3,4] // Implemented set

When the monitored property is an array, these methods push, pop, shift, unshift, splice, sort and reverse will not trigger set. vue calls these methods of modifying the original array mutation methods

2. Every attribute of the object must be traversed

Object.keys(obj).forEach(key=>{
    Object.defineProperty(obj,key,{
        //....
    })
})

3. Nested objects must be traversed in depth

let person = {
    name:{
        firstName:'chan',
        lastName:'louis'
    }
}

When the mutation method is encountered, the old version of vue hijacks the data by rewriting the method.

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {
  // Here is the prototype method of the native Array
  let original = Array.prototype[method];
  // Define the encapsulated methods such as push and pop on the properties of arrayAugmentations
  // Note: it's instance property, not prototype property
  arrayAugmentations[method] = function () {
    console.log('has change');
    // Call the corresponding native method and return the result
    return original.apply(this, arguments);
  };
});
let list = ['a', 'b', 'c'];
// Point the prototype pointer of the array we want to listen to to the empty array object defined above
// In this way, when we call push and pop methods, we can walk into the method we just defined, and add a sentence of console.log.
list.__proto__ = arrayAugmentations;
list.push('d');  // I've been changed!
// list2 is a normal array, so the call push will not go to our method.
let list2 = ['a', 'b', 'c'];
list2.push('d');  // Do not output content

Proxy data proxy

Proxy means agent. Personal understanding: create a proxy proxy object (proxy instance), accept the two parameters of the object you want to listen to and the handle you want to listen to. When the object you want to listen to changes, it will be intercepted by the proxy agent to meet the needs.

var arr = [1,2,3]
var handle = {
    //Target target object key property name the object actually accepted by receiver
    get(target,key,receiver) {
        console.log(`get ${key}`)
        // Reflect is equivalent to mapping to the target object
        return Reflect.get(target,key,receiver)
    },
    set(target,key,value,receiver) {
        console.log(`set ${key}`)
        return Reflect.set(target,key,value,receiver)
    }
}
//The object to be intercepted by arr, and the handle defines the intercepting behavior.
var proxy = new Proxy(arr,handle)
proxy.push(4) //You can turn to the console to test what will be printed out.

1. Using proxy can solve the problem that defineProperty cannot listen to arrays and avoid rewriting array methods.
2. No need to traverse the key.
3. In addition to get and set, the interception processor of Proxy handle supports multiple interception methods. Please refer to the official documents for details.( https://developer.mozilla.org...)
4. Nested query. In fact, proxy get() does not support nested queries. resolvent:

let handler = {
  get (target, key, receiver) {
    // Recursively create and return
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], handler)
    }
    return Reflect.get(target, key, receiver)
  }
}

Posted by flashbot on Fri, 01 Nov 2019 14:51:46 -0700