This is similar to the data binding function in popular front-end frameworks (e.g. React, Vue, etc.). DOM rendering is automatically updated when data is updated. How to implement data binding?

This paper gives two ideas:

  • ES5 Object. DefineProperty
  • The Proxy ES6

ES5 Object. DefineProperty

The object.defineProperty () method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object

– the MDN

Object.defineProperty(obj, prop, descriptor)
Copy the code

Among them:

  • obj: The object for which attributes are to be defined
  • prop: The name or of the property to be defined or modifiedSymbol 
  • descriptor: Property descriptor to define or modify
var user = { 
    name: 'sisterAn' 
}

Object.defineProperty(user, 'name', {
    enumerable: true.configurable:true.set: function(newVal) {
        this._name = newVal 
        console.log('set: ' + this._name)
    },
    get: function() {
        console.log('get: ' + this._name)
        return this._name
    }
})

user.name = 'an' // set: an
console.log(user.name) // get: an
Copy the code

Listen for each child of a variable if it is complete:

// Monitor object
function observe(obj) {
   // Iterate over the object, redefining each property value of the object using get/set
    Object.keys(obj).map(key= > {
        defineReactive(obj, key, obj[key])
    })
}

function defineReactive(obj, k, v) {
    // Recursive subattributes
    if (typeof(v) === 'object') observe(v)
    
    // redefine get/set
    Object.defineProperty(obj, k, {
        enumerable: true.configurable: true.get: function reactiveGetter() {
            console.log('get: ' + v)
            return v
        },
        // When a value is reset, the collector's notification mechanism is triggered
        set: function reactiveSetter(newV) {
            console.log('set: ' + newV)
            v = newV
        },
    })
}

let data = {a: 1}
// Monitor object
observe(data)
data.a // get: 1
data.a = 2 // set: 2
Copy the code

Traversal through map, listening for subchild attributes through deep recursion

Note that Object.defineProperty has the following defects:

  • Internet Explorer 8 and earlier are not supported
  • Unable to detect additions or deletions of object properties
  • If you modify the arraylengthObject.definePropertyCannot listen on array length), as well as array’spushSuch a mutation method is unable to triggersetter

How does vue2. X solve this problem?

How to monitor array changes in vue2. X

Using the method of function hijacking, rewrite the array method, Vue will be in the data array prototype chain rewrite, pointing to their own definition of the array prototype method. This allows dependency updates to be notified when the array API is called. If the array contains a reference type, a recursive traversal of the array is monitored. This allows you to monitor array changes.

For arrays, Vue internally rewrites the following functions to distribute updates

// Get the array prototype
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// Rewrite the following functions
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsToPatch.forEach(function (method) {
  // Cache native functions
  const original = arrayProto[method]
  // Rewrite the function
  def(arrayMethods, method, function mutator (. args) {
  // Call the native function first to get the result
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    // Listen for new data when the following functions are called
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // Manually send updates
    ob.dep.notify()
    return result
  })
})
Copy the code

How does vue2. X solve the problem that adding attributes to objects does not trigger component rerendering

Restricted by modern JavaScript (Object.Observe has been deprecated), Vue cannot detect additions or deletions of Object attributes.

Since Vue performs getter/setter conversions on the property when it initializes the instance, the property must exist on the data object for Vue to convert it to reactive.

Vue does not allow dynamic root-level reactive attributes to be added to already created instances. However, you can add reactive properties to nested objects using the vue.set (Object, propertyName, value) method.

Vm.$set() implementation principle

export function set(target: Array<any> | Object, key: any, val: any) :any {
  // Target is an array
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // Change the length of the array so that splice() is not executed incorrectly due to index > array length
    target.length = Math.max(target.length, key);
    // Use the array splice method to trigger the response
    target.splice(key, 1, val);
    return val;
  }
  // Target is an Object. Key must be on target or target.prototype and must not be directly assigned to object. prototype
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val;
    return val;
  }
  If none of the above is true, start creating a new property for target
  // Get an Observer instance
  const ob = (target: any).__ob__;
  // Target itself is not reactive data, directly assigned
  if(! ob) { target[key] = val;return val;
  }
  // Perform reactive processing
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}
Copy the code
  • If the target is an array, use the variation method implemented by VUEspliceImplementing responsiveness
  • If the target is an object, the attribute is judged to exist, that is, it is reactive and directly assigned
  • iftargetIt’s not reactive, it’s directly assigned
  • Is called if the property is not reactivedefineReactiveMethod for reactive processing

The Proxy ES6

It is well known that Vue3.0 uses Proxy instead of defineProperty for data binding because Proxy can directly listen for changes in objects and arrays and has up to 13 interception methods. And as a new standard, browser vendors will continue to focus on performance optimization.

Proxy

Proxy objects are used to create a Proxy for an object to intercept and customize basic operations (such as property lookup, assignment, enumeration, function calls, and so on)

– MDN

const p = new Proxy(target, handler)
Copy the code

Among them:

  • target: to useProxyWrapped target object (can be any type of object, including a native array, a function, or even another proxy)
  • handler: an object that usually has functions as attributes, and the functions in each attribute define the agents that perform the various operationspThe behavior of the
var handler = {
    get: function(target, name){
        return name in target ? target[name] : 'no prop! '
    },
    set: function(target, prop, value, receiver) {
        target[prop] = value;
        console.log('property set: ' + prop + '=' + value);
        return true; }};var user = new Proxy({}, handler)
user.name = 'an' // property set: name = an

console.log(user.name) // an
console.log(user.age) // no prop!
Copy the code

As mentioned above, Proxy provides a total of 13 interception behaviors, which are:

  • getPrototypeOf / setPrototypeOf
  • isExtensible / preventExtensions
  • ownKeys / getOwnPropertyDescriptor
  • defineProperty / deleteProperty
  • get / set / has
  • apply / construct

If you’re interested, check out MDN and try them out. I won’t repeat them here

Consider two other questions:

  • A Proxy only proxies the first layer of an object, so how does that work?
  • How do you prevent multiple get/set triggers when monitoring arrays?

Vue3 Proxy

For the first question, we can determine if the current reflect. get return value is Object, and if so, we can proxy it by Reactive.

For the second question, we can determine if it is hasOwProperty

Here we write a case to customize the behaviors of acquiring, adding and deleting through proxy

const toProxy = new WeakMap(a);// Store the proxied object
const toRaw = new WeakMap(a);// Store the proxied object
function reactive(target) {
  // Create a responsive object
  return createReactiveObject(target);
}
function isObject(target) {
  return typeof target === "object"&& target ! = =null;
}
function hasOwn(target,key){
  return target.hasOwnProperty(key);
}
function createReactiveObject(target) {
  if(! isObject(target)) {return target;
  }
  let observed = toProxy.get(target);
  if(observed){ // Determine whether the proxy is used
    return observed;
  }
  if(toRaw.has(target)){ // Determine whether to duplicate the proxy
    return target;
  }
  const handlers = {
    get(target, key, receiver) {
        let res = Reflect.get(target, key, receiver);
        track(target,'get',key); // Rely on collection ==
        returnisObject(res) ? reactive(res):res; },set(target, key, value, receiver) {
        let oldValue = target[key];
        let hadKey = hasOwn(target,key);
        let result = Reflect.set(target, key, value, receiver);
        if(! hadKey){ trigger(target,'add',key); // Trigger add
        }else if(oldValue ! == value){ trigger(target,'set',key); // Trigger the change
        }
        return result;
    },
    deleteProperty(target, key) {
      console.log("Delete");
      const result = Reflect.deleteProperty(target, key);
      returnresult; }};// Start the proxy
  observed = new Proxy(target, handlers);
  toProxy.set(target,observed);
  toRaw.set(observed,target); // create a mapping table
  return observed;
}
Copy the code

conclusion

Advantages of Proxy over defineProperty:

  • Based on theProxyReflect, can listen on native arrays, can listen on object attributes added and removed
  • No deep traversal listening required: judge the currentReflect.getIs the return value ofObjectIf yes, pass againreactiveMethod as a proxy, so as to achieve depth observation
  • Only in thegetterThe next layer of the object is hijacked (optimized for performance)

Therefore, it is recommended to use Proxy to monitor variable changes

reference

  • MDN
  • Vue – Next (VUE 3.0) is perfect

Three minutes a day