“This is the 26th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

preface

I have studied the responsive API and source code of Vue3.0 in three previous articles. The responsive principle (I) Responsive principle (II) Responsive Principle (III)

Vue3.0’s reactive bottom layer uses new Proxy() to intercept getters and setters of data. Dependency collection is carried out during the process. If data changes, corresponding dependencies will be notified to change. If you’ve read about the reactive principles of Vue2.0, you know that the reactive underlayer of Vue2.0 is implemented with Object.defineProperty. Why the big change? This article will compare responsive implementations of Vue2.0 and Vue3.0 by studying their source code.

Reactive source code

Vue2.0 Project ADDRESS Vue3.0 project address

MDN Object.defineProperty() Proxy

Vue2.0

  • Source location\src\core\observer\index.js
  • Response functiondefineReactive, the initialization needs to be called frequently becauseObject.definePropertyOnly one property in the object can be intercepted at a time, and if the value of the current propertyvalIt’s also an object that calls an observer functionobserve(val)Creates an observer for the object
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {
  // Create a dependency
  const dep = new Dep()
  ...
  // If the property is still an object, recurse
  letchildOb = ! shallow && observe(val)// Data interception operation
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // Collect dependencies
        dep.depend()
        if (childOb) { 
          // Collect dependencies recursivelychildOb.dep.depend() ... }}return value
    },
    set: function reactiveSetter (newVal) {...// Notifications depend on updates
      dep.notify()
    }
  })
}
Copy the code
  • Observer functionobserveWill create and return oneobObserverInstances of the class
export function observe (value: any, asRootData: ? boolean) :Observer | void {
  if(! isObject(value) || valueinstanceof VNode) {
    return
  }
  let ob: Observer | void.// The observer object
    ob = new Observer(value)
  ...
  return ob
}
Copy the code
  • ObserverThe class will execute itwalkMethod,walkThe current object is iterated over and the call continuesdefineReactiveThis creates a recursive call
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
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      / / array. }else {
      // If it is an object
      this.walk(value)
    }
  }

  /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value  type is Object. */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    // Loop through the current object for responsive interception
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  ...
}
Copy the code
  • Here are some of the drawbacks of using object.defineProperty for reactive operations

    • Only one key can be monitored at a time
    • Initialization requires cyclic recursive traversal of all keys in OBJ, which is slow and will generate closures and occupy large resources
    • No responsive listening for data of Collection type (set Map)

Vue2 does some compatibility with some congenital defects of Object.defineProperty

  • Unable to detect dynamic property additions and deletions, newsetdelmethods
export function set (target: Array<any> | Object, key: any, val: any) :any {...// Redefine responsiveness for new attributes
  defineReactive(ob.value, key, val)
  // Notifications depend on updates
  ob.dep.notify()
  return val
}
export function del (target: Array<any> | Object, key: any) {...// Manually delete attributes
  delete target[key]
  if(! ob) {return
  }
  // Notifications depend on more
  ob.dep.notify()
}
Copy the code
  • Unable to detect callArrayChanges to the array, such aspush popAnd so on, Vue2 passes rightArray.prototypeThe array prototype method is overridden to implement reactive interception (but forThe array subscriptThe operation is still not listened to and still needs to be usedset)
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

// Intercepts methods on prototypes and dispatches events
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (. args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // Notifications depend on updates
    ob.dep.notify()
    return result
  })
})
...
/ / the 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
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      // Wrap the array prototype that you overwrote earlier
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // Add responsiveness to array elements
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  ...

  // Add responsiveness to array elements
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
Copy the code

Vue3.0

Because of these disadvantages of Object.defineProperty, Vue3.0 uses Proxy for responsiveness, which naturally solves these problems, but Proxy does not support IE11 and below

  • In the previous articleVue3.0 Source code learning — Responsive Principle (1)If you’re in a reactive APIreactiveIs passed in as a normal object, and ends up using oneProxyAnd in the correspondingget,set,deleteProperty,has,ownKeysFor responsive interception operations
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {...// Create a proxy object, using the original object passed as the proxy target
  const proxy = new Proxy(
    target,
    Handlers use baseHandlers, which is the mutableHandlers we passed in earlier
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
...
// handler
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
Copy the code

summary

  • implementation
    • Vue2: object.defineProperty (obj, key, {})
    • Vue3: new Proxy(obj, {})
  • Object.definePropertyProxyThe advantages and disadvantages of
    • DefineProperty can only monitor one key at a time. During initialization, all keys in OBJ need to be cyclically and recursively traversed, which is slow, consumes large resources and has closures
    • DefineProperty cannot detect dynamic attribute additions and deletions
    • DefineProperty doesn’t support arrays very well and requires an additional array responsive implementation
    • Vue2 does not support Collection types such as set and map
    • Proxy does not support Internet Explorer 11 or later versions