Some research on Observer in Vue2

Observer

At the break point, we will find that the new Observer will be restarted when traversing the object, and the Observer has its own dep object. Since we can monitor the data, why do we need dep

Let's talk about the conclusion first. In fact, this is for us to bind new data by adding data such as $set and array operations

Let's think about how we can remind the view to update through Dep and notify if a new data comes in and it won't bind the data Bi directionally when initData was in the past. At this time, we remember the role of Dep, but there is no Dep for the new data. How can we prompt the data to change through Dep's notify.

  1. Vue is implemented by adding a dep to the data and defining its own data when the sub object is obj__ ob__ attribute
// new Observer
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
  constructor(value: any) {
    this.value = value;
    // Instantiate a Dep
    this.dep = new Dep();
    this.vmCount = 0;
    // Define a__ ob__  attribute
    def(value, "__ob__", this);
    //... omit the following
 }
}
  1. When traversing an object, if it is an object, give the object new Observer() and add its own Dep
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter ? : ? Function,
  shallow ? : boolean
) {
 //... omit something
 // Judge whether val is an object. If yes, give the object new observer() and a dep object
 // This childOb is to bind the corresponding Dep, which is equivalent to adding a Dep outside. If there is new data, it will pass through the corresponding Watcher
  let childOb = !shallow && observe(val);
 //... omit something
}
  1. Through the implementation of the two processes of appeal, the final goal is that each object has its own__ ob__ And dep properties
  2. When using data, Wacher will be added through the get method and childOb.dep
if (childOb) {
          // The observer object of the nested object in the object. If it exists, it is also dependent collected
          childOb.dep.depend();
          //... handle other
}
  1. At this time, let's look at the set and delete methods, both through__ ob__ This element gets dep to notify
export function set(target: Array < any > | Object, key: any, val: any): any {
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }

  // If the target is an array, use the slice mutation method of the array to trigger the response
  // Vue.set([1,2,3], 1, 5)
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // Modify the length of the array to avoid that the array index key is greater than the length of the array, resulting in an error in the execution of splcie()
    target.length = Math.max(target.length, key);

    target.splice(key, 1, val);
    return val;
  }

  // If the key already exists in the target, update the value of target[key] to val
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }

  // Read the target__ ob__, This is mainly used to determine whether the target is a responsive object
  const ob = (target: any).__ob__;

  // The target object to be operated on cannot be a Vue instance or the root data object of a Vue instance
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid adding reactive properties to a Vue instance or its root $data " +
        "at runtime - declare it upfront in the data option."
      );
    return val;
  }

  // When the target is not a responsive object and the object itself does not have this new attribute key
  // The new property will be set, but it will not be processed responsively
  if (!ob) {
    target[key] = val;
    return val;
  }

  // target is a responsive object, and the new attribute key does not exist in the object itself
  // Define a new attribute for the object, and set the new attribute as responsive through the defineReactive method
  // ob.dep.notify notification update
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}

/**
 * Delete a property and trigger change if necessary.
 */
// Delete the key attribute of target through Vue.delete or vm.$delete
export function del(target: Array < any > | Object, key: any) {
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }

  // If the target is an array, delete the corresponding key item through the mutation method splice of the array, and trigger a responsive update
  // Vue.delete([1,2,3], 1)
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1);
    return;
  }

  // Read the target__ ob__, This is mainly used to determine whether the target is a responsive object
  const ob = (target: any).__ob__;

  // The target object to be operated on cannot be a Vue instance or the root data object of a Vue instance
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid deleting properties on a Vue instance or its root $data " +
        "- just set it to null."
      );
    return;
  }
  // If the key attribute does not exist on the target, end directly
  if (!hasOwn(target, key)) {
    return;
  }
  // Delete the key item of the object directly through delete
  delete target[key];
  // target is not a responsive object and does not need to be updated
  if (!ob) {
    return;
  }
  // target is a responsive, notification update
  ob.dep.notify();
}
  1. In addition, this is also used in the update of the array. vue will redefine the methods in 7 of the array
methodsToPatch.forEach(function (method) {
  // Method of buffering original array
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    // push, unshfit and splice will insert new elements, and the second parameter of splice starts to represent the inserted element value
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args // For example: args is [{...}]
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // Responsive processing of inserted elements
    if (inserted) ob.observeArray(inserted)

    // Notify updates through dep.notify
    ob.dep.notify()
    return result
  })
})

a key

  1. Give each object a Dep and__ ob__
  2. And this object is given to the upper level through childobj
  3. childobj is to enable each object to have a Watcher owned by the previous get. At this time__ ob__ Dep used Watcher
  4. When adding new elements, we can use this__ ob__ Dep to notify refers to operations such as view change

The logic is very convoluted. If the boss has a proper description, you are welcome to point out relevant errors and description points 😆😆

Tags: Javascript Front-end Vue Vue.js

Posted on Sat, 04 Dec 2021 20:55:31 -0500 by Denholm