First published at: github.com/USTB-musion…

Writing in the front

Vue. Js is an MVVM framework. The core idea is data-driven views, and the data model is just ordinary JavaScript objects. When they are modified, the view is updated. And the key to doing that is”Responsive system“.

We might have questions like this during development:

  1. What objects does vue.js turn into responsive objects?
  2. How does vue.js modify data in a responsive manner?
  3. What does the bottom half of the picture look like?
  4. Why is the data sometimes delayed (i.e., when nextTick is used)?

To achieve a simple version of the responsive system

The code for a responsive system core is defined in SRC /core/ Observer:

This part of the code is very much, in order to let you have an impression of the responsive system, HERE I first implement a simple version of the responsive system, sparrow is small five organs, can be combined with the lower part of the beginning of the picture to analyze, write notes for everyone to understand.

 /** * Dep is a bridge between data and Watcher. It implements two main functions: * 1. The addSub method adds a Watcher subscription operation to the current Dep object. * 2. Notify all Watcher objects in the subs of the Dep object to trigger the update operation. * /
class Dep {
    constructor () {
        // An array of Watcher objects
        this.subs = [];
    }
    addSub (sub) {
        // Add the Watcher object to subs
        this.subs.push(sub);
    }
    // Notify all Watcher objects to update the view
    notify () {
        this.subs.forEach((sub) = >{ sub.update(); }}})// The observer object
class Watcher {
    constructor () {
        // dep. target represents the Watcher currently being calculated globally (the current Watcher object), used in GET
        Dep.target = this;
    }
    // Update the view
    update () {
        console.log("View updated");
    }
}

Dep.target = null;

class Vue {
    // Vue constructs the class
    constructor(options) {
        this._data = options.data;
        this.observer(this._data);
        // Instantiate the Watcher observer object. Dep.target will point to the Watcher object
        new Watcher();
        console.log('render'.this._data.message);
    }
    // Encapsulate Object.defineProperty to dynamically add setters and getters to the Object
    defineReactive (obj, key, val) {
        const dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true.configurable: true.get: function reactiveGetter () {
                // Add dep. target (the Watcher object currently in progress) to dep
                dep.addSub(Dep.target);
                return val;         
            },
            set: function reactiveSetter (newVal) {
                if (newVal === val) return;
                // Notify deP's notify method on set to notify all Wacther objects to update viewsdep.notify(); }}); }// Perform defineReactive to traverse the passed object
    observer (value) {
        if(! value || (typeofvalue ! = ='object')) {
            return;
        }
        Object.keys(value).forEach((key) = > {
            this.defineReactive(value, key, value[key]); }); }}let obj = new Vue({
  el: "#app".data: {
      message: 'test'
  }
})
obj._data.message = 'update'
Copy the code

Execute the above code and print the following information:

Render Test view updatedCopy the code

The following combined with vue.js source code to analyze its process:

Object.defineProperty()

We all know that the core of responsiveness is using the object.defineProperty () method from ES5, which is why VUe.js doesn’t support IE9 below, and there are no good patches to fix this. Refer to the MDN documentation for details. Here’s how it works:

 /* obj: target object prop: Property name descriptor of the target object to be operated on: descriptor Return Value The object is passed in */
Object.defineProperty(obj, prop, descriptor)
Copy the code

So descriptor has two very core properties: get and set. Getter methods are fired when we access a property, and setter methods are fired when we modify a property. When an object has getter methods and setter methods, we call it a reactive object.

Start with new Vue()

Vue is actually a class to implement in the Function, defined in SRC/core/instance/index in js:When using the new keyword to instantiate the Vue, executes _init method, defined in SRC/core/instance/init. Js, key code below:

In the call to initState () method, we look at initState what did () method, defined in SRC/core/instance/state. The js, key code below:

As you can see, the initState method mainly initializes properties such as props, methods, data, computed, and watcher. In the call to initData method, take a look at what did initData method, defined in SRC/core/instance/state. The js, key code below:

This code does two main things: one is to delegate the data on _data to the VM, and the other is to turn all data into observables via Observe. The key in data cannot conflict with the key in props or methods, otherwise warning will be generated.

Observer

Next look at the definition of the Observer, in/SRC/core/Observer/index in js:

/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has 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)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /** * Walk through each property 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)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /** * Observe a list of Array items. */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
Copy the code

Pay attention to the English notes, especially the difficult places have been written in English notes. The Observer adds getters and setters for properties of objects that rely on collecting and distributing updates. The walk method defines the attribute traversal of the passed object, and the observeArray method observes the traversal of the passed array.

defineReactive

Look at the next defineReative method, defined in SRC/core/observer/index. In js:

  letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      /* eslint-enable no-self-compare */
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } })Copy the code

The child of the object recursively observes and returns the Observer of the child node:

childOb = ! shallow && observe(val)Copy the code

If the current Watcher object exists, it is dependently collected and its children are dependently collected, if it is an array, then the array is dependently collected, if the array’s children are still arrays, then it is iterated over:

if (Dep.target) {
    dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
    }
}
Copy the code

Observe the new value when executing the set method to ensure that the new value is responsive:

childOb = ! shallow && observe(newVal)Copy the code

The DEP executes notify to all Watcher observer objects:

dep.notify()
Copy the code

Dep

Dep is the bridge between Watcher and data. Dep.target represents the Watcher that is being calculated globally. See depend on the definition of the collector Dep, in/SRC/core/observer/Dep. In js:

export default class Dep { static target: ? Watcher; id: number; subs: Array<Watcher>; Constructor () {this.id = uid++ this.subs = []} // add an observer to the constructor (sub: Watcher) {this.subs.push(sub)} // Remove an observer removeSub (sub: Watcher) {remove(this.subs, sub)} Add Watcher observer depend () {if (dep.target) {dep.target.adddep (this)}} // Notify all subscribers () {// stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // the current target watcher being evaluated. // this is globally unique because there < // Show time < // Could be only one // Watcher being evaluated at any time.dep. target = nullCopy the code

Watcher

The Watcher is an observer object. After a dependency collection, the Watcher object is stored in Deps, and Deps informs the Watcher instance of data changes. Defined in the/SRC/core/observer/watcher in js:

/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  computed: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  dep: Dep;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object, isRenderWatcher? : boolean) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !! options.deepthis.user = !! options.userthis.computed = !! options.computedthis.sync = !! options.syncthis.before = options.before
    } else {
      this.deep = this.user = this.computed = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.computed // for computed watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production'
      ? expOrFn.toString()
      : ' '
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {} process.env.NODE_ENV ! = ='production' && warn(
          `Failed watching path: "${expOrFn}"` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

  /** * Evaluate the getter, and re-collect dependencies. */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "The ${this.expression}"`)}else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /** * Add a dependency to this directive. */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)}}}/** * Clean up for dependency collection. */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)}}let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /** * Subscriber interface. * Will be called when a dependency changes. */
  update () {
    /* istanbul ignore else */
    if (this.computed) {
      // A computed property watcher has two modes: lazy and activated.
      // It initializes as lazy by default, and only becomes activated when
      // it is depended on by at least one subscriber, which is typically
      // another computed property or a component's render function.
      if (this.dep.subs.length === 0) {
        // In lazy mode, we don't want to perform computations until necessary,
        // so we simply mark the watcher as dirty. The actual computation is
        // performed just-in-time in this.evaluate() when the computed property
        // is accessed.
        this.dirty = true
      } else {
        // In activated mode, we want to proactively perform the computation
        // but only notify our subscribers when the value has indeed changed.
        this.getAndInvoke(() = > {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)}}/** * Scheduler job interface. * Will be called by the scheduler. */
  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }

  getAndInvoke (cb: Function) {
    const value = this.get()
    if( value ! = =this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      this.dirty = false
      if (this.user) {
        try {
          cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "The ${this.expression}"`)}}else {
        cb.call(this.vm, value, oldValue)
      }
    }
  }

  /** * Evaluate and return the value of the watcher. * This only gets called for computed property watchers. */
  evaluate () {
    if (this.dirty) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

  /** * Depend on this watcher. Only for computed property watchers. */
  depend () {
    if (this.dep && Dep.target) {
      this.dep.depend()
    }
  }

  /** * Remove self from all dependencies' subscriber list. */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)}let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)}this.active = false}}}Copy the code

The last

Now that the principles of responsive systems are sorted out, let’s go back and see if the bottom half of the picture is clear.

You can pay attention to my public account “Muchen classmates”, goose factory code farmers, usually record some trivial bits, technology, life, perception, grow together.