Vue implements data responsiveness, detecting data changes through data hijacking, collecting dependencies and updating views through publish/subscribe mode. In other words, Observe, Watcher and Compile work together.

  • Observe implements data hijacking, recurses to object properties, binds setter and getter functions, and notifies subscribers of property changes
  • Compile parses the template and puts it into the templatevariableSwitch todataBind the update function, add subscribers, and execute the update function when notified
  • Watcher acts as a bridge between Observe and Compile, subscribing to the change of Observe and triggering the Compile update function

Data hijacking/proxy Observer

The first step to realize responsiveness is to detect changes in data. In Vue2. X, Object attributes are monitored by ES5 method object.defineProperty (), and in Vue3

Object.defineProperty

function observe(obj) {
  if(! obj ||typeofobj ! = ="object") {
    return;
  }
  Object.keys(obj).forEach((key) = > {
    defineReactive(obj, key, obj[key]);
  });
  function defineReactive(obj, key, value) {
    // Recursive subattributes
    observe(value);
    / / subscriber
    const dp = new Dep();
    Object.defineProperty(obj, key, {
      configurable: true./ / can be deleted
      enumerable: true.// Enumerative traversal
      get: function () {
        /* Store dep. target (the current Watcher object into the subs of the Dep) */
        dp.addSub(Dep.target);
        return value;
      },
      set: function (newValue) {
        // Recurse to a new subattribute
        observe(newValue);
        if(value ! == newValue) { value = newValue;/* Trigger notify of deP on set to notify all Watcher objects to update view */dp.notify(); }}}); }}Copy the code

Proxy Implement Proxy


let target = { name: " xiao" };

let handler = {
  get(target, key) {
    if (typeof target[key] === "object"&& target[key] ! = ="null") {
      return new Proxy(target[key], handler);
    }
    return target[key];
  },
  set: function (target, key, value) { target[key] = value; }}; target =new Proxy(target, handler);
Copy the code

Rely on collecting dePs

//Dep subscriber, dependent collector
class Dep {
  constructor() {
    /* An array of Watcher objects */
    this.subs = [];
  }
  /* Add a Watcher object to subs */
  addSub(sub) {
    this.subs.push(sub);
  }
  /* Add a Watcher object to subs */
  notify() {
    this.subs.forEach((sub) = >{ sub.update(); }); }}// Add a Watcher subscription to the current Dep object with the addSub method;
// Notify all Watcher objects in the subs of the current Dep object to trigger the update operation.
Copy the code

The Watcher subscriber

class Watcher {
  constructor(obj, key, cb) {
    /* Assign a Watcher object to dep. target when new, use */ in ObserveGet
    Dep.target = this;
    this.obj = obj;
    this.key = key;
    this.cb = cb;
    // Trigger getter, depend on collection
    this.value = obj[key];
    // Dep. Target is empty after collection to prevent repeated collection
    Dep.target = null;
  }
  update() {
    // Get a new value
    this.value = obj[this.key];
    console.log("View Update"); }}Copy the code

Compile template

  • Regular matching parses VUE instructions and expressions
  • Initialize render by replacing variables with data
  • Create the Watcher subscription update function
// Instruction handler class
const compileUtile = {
    getVal(expr,vm){
        // Reduce is good
        return expr.split('. ').reduce((data,curentval) = >{
            return data[curentval];
        },vm.$data)
    },
    html(node,expr,vm){
        new Watcher(vm,expr,(newVal) = >{
            this.updater.htmlUpdate(node,newVal);
        })
        const value = this.getVal(expr,vm);
        this.updater.htmlUpdate(node,value);
    },
  
    // Update function
    updater: {htmlUpdate(node,value){ node.innerHTML= value; }}},//Compile the directive parser
class Compile{
// Various re matches vUE instructions and expressions to replace data
}


Copy the code

DefineProperty and Proxy difference?

  • Proxy can listen directly for objects, not properties, and can listen for additions to properties
  • Proxy can listen on arrays
  • Proxy has many interception methods that Object.defineProperty does not
  • The Proxy returns a new Object, which can be manipulated directly. Object.defineproperty can only iterate over Object property changes

Why rely on collections?

The purpose of data hijacking is to trigger view update when attributes change. Depending on the collection, we can collect where the relevant attributes are used. When attributes change, we can notify all places to update the view

Dep and Watcher (many-to-many)

  • In data, one key corresponds to one Dep instance, and one Dep instance corresponds to multiple Watcher instances (one attribute is used in multiple expressions)
  • An expression corresponds to a Watcher instance, and a Watcher pair uses multiple Dep instances (multiple attributes in an expression)

When watcher and Dep are created

  • Dep is created when the property of data is initialized for data hijacking
  • Watcher is created when curly brace expressions/general instructions are parsed at initialization

How to implement the array listening

Because Object.defineProperty cannot listen for array length changes, Vue overwrites the array method by using function hijacking. Vue overwrites the array stereotype chain in Data and points to its own array stereotype 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.

 1 // src/core/observer/array.js
 2 
 3 Array.prototype (array.prototype)
 4 const arrayProto = Array.prototype
 5 // Create an empty object arrayMethods and point the arrayMethods prototype to array.prototype
 6 export const arrayMethods = Object.create(arrayProto)
 7 
 8 // Lists the array method names that need to be overridden
 9 const methodsToPatch = [
10   'push'.11   'pop'.12   'shift'.13   'unshift'.14   'splice'.15   'sort'.16   'reverse'
17 ]
18 // iterate over the above array method names and add the overwritten arrayMethods to the arrayMethods object in turn
19 methodsToPatch.forEach(function (method) {
20   // Save a copy of the original array method corresponding to the current method name
21   const original = arrayProto[method]
22   Function mutator() {} is the overridden method defined on the arrayMethods object
23   def(arrayMethods, method, function mutator (. args) {
24     // Call the original array method, pass in the argument args, and assign the execution result to result
25     const result = original.apply(this, args)
26     // When an array calls the overridden method, this refers to the array. When the array is reactive, its __ob__ attribute is retrieved
27     const ob = this.__ob__
28     let inserted
29     switch (method) {
30       case 'push':
31       case 'unshift':
32         inserted = args
33         break
34       case 'splice':
35         inserted = args.slice(2)
36         break
37     }
38     if (inserted) ob.observeArray(inserted)
39     // Notifies its subscribers of changes to the current array
40     ob.dep.notify()
41     // Return the execution result
42     return result
43   })
44 })
Copy the code

Def is a custom array of methods that override value via Object.defineProperty


function def(obj,key,val,enumble){
Object.defineProperty(obj,key,{
 enumble:!!!!! enumble,configrable:true.writeble:true.val:val
 
})
}
Copy the code

Add an array to the observe method,

  • Can get to the__proto__Properties, just put__protp__Property points to overridden methods
  • Can’t get__proto__Property, which defines the overridden method on the object instance

// src/core/observer/index.js
export class Observer {...constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  ...
}
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

Copy the code