Vue is currently one third of the country’s front-end Web end, but also as one of my main technology stack, in daily use, also curious about why, in addition to the recent emergence of a large number of vUE source code reading articles, I take this opportunity to draw some nutrition from everyone’s article and discussion, At the same time, some of the ideas of reading source code are summarized, produce some articles, as the output of their own thinking, my level is limited, welcome to leave a message to discuss ~

Target Vue version: 2.5.17-beta.0

Vue source note: github.com/SHERlocked9…

Note: the syntax of the source code in the article uses Flow, and the source code is truncated as required (in order not to be confused @_@), if you want to see the full version of the github address above, this is a series of articles, the article address is at the bottom of ~

If you are interested, you can join the wechat group at the end of the article and discuss with us

1. Responsive systems

Vue. Js is an MVVM framework that doesn’t care about view changes, but updates the view with data. This makes state management very simple. Stealing a picture from the official website

Each component instance has a corresponding Watcher instance object, which records properties as dependencies during component rendering and then, when setters for dependencies are called, informs Watcher to recalculate, causing its associated components to be updated.

Here are three important concepts Observe, Dep, Watcher, located in the SRC/core/observer/index, js, SRC/core/observer/Dep., js, SRC/core/observer/Watcher. Js

  • ObserveClass adds attributes to reactive objectsgetter/setterUsed for dependency collection and distribution of updates
  • DepClass to collect the dependencies of the current reactive object
  • WatcherClass is observer, instance is divided into three types: render watcher, compute property Watcher, listener watcher

2. Code implementation

2.1 initState

Response to a type of entrance is located in the SRC/core/instance/init. Js initState:

// src/core/instance/state.js

export function initState(vm: Component) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)              // Initialize props
  if (opts.methods) initMethods(vm, opts.methods)        // Initialize methods
  if (opts.data) initData(vm)                            // Initialize data
  if (opts.computed) initComputed(vm, opts.computed)     // Initialize computed
  if (opts.watch) initWatch(vm, opts.watch)              // Initialize watch}}Copy the code

It regularly defines several methods to initialize props, methods, data, computed, and wathcer. Here’s a look at the initData method to see a leopard

// src/core/instance/state.js

function initData(vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
                    ? getData(data, vm)
                    : data || {}
  observe(data, true /* asRootData */)             // Perform reactive processing on data
}
Copy the code

An observe method attempts to create an Observer instance __ob__, and returns a new Observer instance if it succeeds. Returns the existing Observer instance if it already exists

2.2 the Observer/defineReactive

// src/core/observer/index.js

export function observe (value: any, asRootData: ? boolean) :Observer | void {
  let ob: Observer | void
  ob = new Observer(value)
  return ob
}
Copy the code

This method uses data as an argument to instantiate an instance of an Observer object. Observer is a Class used for dependency collection and notify. The Observer constructor uses defineReactive to instantiate the object’s key reactivity. Add getter/setter recursively to the property of the object. When data is evaluated, the getter is triggered and the dependency is collected. When the value is modified, the getter is triggered first and then the setter is triggered and updates are issued

// src/core/observer/index.js

export class Observer {
  value: any;
  dep: Dep;

  constructor (value: any) {
    value: any;
    this.dep = new Dep()
    def(value, '__ob__'.this)    // the def method is guaranteed not to be enumerable
    this.walk(value)
  }

  // Iterate over each property of the object and convert them to getters/setters
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) { // Reactalize all traversable objects
      defineReactive(obj, keys[i])
    }
  }
}

export function defineReactive (obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean) {
  const dep = new Dep()         // Define a DEP object in the closure of each reactive key value

  // If a getter/setter was preset for the object, it will be cached and executed in the newly defined getter/setter
  const getter = property && property.get
  const setter = property && property.set

  letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val         Execute if the original object has a getter method
      if (Dep.target) {                    // If there is a watcher reading the current value
        dep.depend()                       // Then do the dependency collection, dep.addSub
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val    / / the getter
      if(newVal === value || (newVal ! == newVal && value ! == value)) {// If the value is the same as the original value, no matter
        return
      }
      if (setter) { setter.call(obj, newVal) }         Execute if the original object has setter methods
      else { val = newVal }
      dep.notify()                                     // If there is a change, notify the update, call watcher.update()}})}Copy the code

In the getter, dependency collection is performed only when there is a value in the dep. target. PushTarget pushes the current value of the Watcher into the dep. target, and pushes the original Watcher into the targetStack. When the current watcher value is finished, the stack is unloaded and the original watcher value is assigned to dep. target. CleanupDeps finally clears the missing watcher in the new newDeps to prevent unwanted watcher triggers from the view

If there is no change to the old value, return. If there is change, deP will notify all Watcher instances in subs that depend on this data to update. QueueWatcher () is asynchronously pushed from the update to the dispatcher observer queue, FlushSchedulerQueue () at nextTick flushSchedulerQueue() removes the watcher from the queue to execute watcher.run and the related hook function

2.3 Dep

Dep is a dependency collection container, or dependency collector, that keeps track of which Watchers depend on their changes, or which watchers subscribe to their changes; Here is a quote from a netizen:

Liuhongyi0101: To put it simply, it is reference counting, who borrowed my money, I will write that person down, and later I will notify them that I have no money when my money is less

And the little book that keeps the borrower down is the subs in the Dep instance here

// src/core/observer/dep.js

let uid = 0            // The id of the Dep instance to facilitate de-duplication

export default class Dep {
  statictarget: ? Watcher// Who is currently collecting dependencies
  id: number
  subs: Array<Watcher>              // Set of observers
  
  constructor() {
    this.id = uid++                             // The id of the Dep instance to facilitate de-duplication
    this.subs = []                              // Stores the Watcher that needs to be notified in the collector
  }

  addSub(sub: Watcher) { ... }  /* Add an observer object */
  removeSub(sub: Watcher) { ... }  /* Removes an observer object */
  depend() { ... }  /* Dependency collection, add yourself to observer dependencies when dep. target is present */
  notify() { ... }  /* Notify all subscribers */
}

const targetStack = []           / / watcher stack

export function pushTarget(_target: ? Watcher) {... }/* Set the Watcher observer instance to dep.target to rely on the collection. This instance is also stored in the target stack */
export function popTarget() {... }/* Remove the observer instance from the target stack and set it to dep.target */
Copy the code

The dependency collected by subs in the Dep instance is watcher, which is an instance of Watcher and will be used to notify updates in the future

2.4 Watcher

// src/core/observer/watcher.js

/* A parsed expression that performs a dependency collection on the observer and triggers a callback function when the expression data changes. It is used in the $watch API as well as the instruction */
export default class Watcher {
  constructor( vm: Component, expOrFn: string | Function, cb: Function, options? :? Object, isRenderWatcher? : Boolean // whether to render watcher flag bit) {this.getter = expOrFn                // execute in the get method
    if (this.computed) {                   // Whether it is a calculated attribute
      this.value = undefined
      this.dep = new Dep()                 // The calculated property was not evaluated during creation
    } else {                               // Attributes are not evaluated immediately
      this.value = this.get()
    }
  }

  /* Get the getter value and re-collect the dependency */
  get() {
    pushTarget(this)                // Set dep. target = this
    let value
    value = this.getter.call(vm, vm)
    popTarget()                      // Remove the observer instance from the target stack and set it to dep.target
    this.cleanupDeps()
    return value
  }

  addDep(dep: Dep) { ... }  /* Add a dependency to the Deps collection */
  cleanupDeps() { ... }  /* Remove useless watcher dependencies that are not in newDeps */
  update() { ... }  /* Scheduler interface to call back when a dependency changes */
  run() { ... }  /* Dispatcher work interface, dispatcher callback */
  getAndInvoke(cb: Function) {... } evaluate() { ... }/* Collect all dePS dependencies for this watcher */
  depend() { ... }  /* Collect all dePS dependencies for this watcher, only calculated attributes use */
  teardown() { ... }  /* Remove itself from all dependent collection subscriptions */
}
Copy the code

UpdateComponent = () => {vm._update(vm._render(), hydrating)}, This method starts with vm._render() generating the render VNode tree, completing data access on the current Vue instance VM, triggering getters for the corresponding responsive objects, and then vm._update() to patch

Note that the get method finally executes getAndInvoke. This method first iterates through the deps stored in watcher and removes the subscription that is no longer in newDep. DepIds = newDepIds; Deps = newDeps, emptying newDepIds and newDeps. Remove old subscriptions that are no longer needed after each new subscription is added, so that watcher is not notified to update in some cases, such as when data on a template dependent that v-if no longer needs changes

2.5 summary

The whole collection process is about like this, you can compare with the above process

Watcher can be used in the following scenarios:

  • render watcherRender Watcher, the watcher for rendering views
  • computed watcherCalculated properties Watcher, because calculated properties are both dependent on and dependent on, and therefore hold oneDepThe instance
  • watch watcherThe listener watcher

If it is dependent on another observer, such as Data, data properties, calculation properties, props, it generates an instance of the Dep in the closure and collects it when the getter is called. And is dependent on the watcher of deposit into their subs this. Subs. Push (sub), in order to change the notice notify in dep. Subs array depends on his own, which has changed, please timely update ~

Any object that depends on another reactive object will generate an observer watcher to count which reactive objects the watcher depends on. The current watcher will be set to the global DEP.target before the watcher is evaluated. And update when the responsive objects on which they depend change


This article is a series of articles that will be updated later to make progress together

  1. Vue source code to read – file structure and operation mechanism
  2. Vue source code read – dependency collection principle
  3. Vue source code read – batch asynchronous update and nextTick principle

Online posts are mostly different in depth, even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to comment out ~

Reference:

  1. Vue2.1.7 source code learning
  2. Vue. Js technology revealed
  3. Analyze the internal operation mechanism of vue.js
  4. Vue. Js document
  5. [large dry goods] hand in hand with you over vUE part of the source code
  6. MDN – Object.defineProperty()
  7. Vue. Js source code learning a – data option State learning

PS: Welcome to pay attention to my official account [front-end afternoon tea], come on together

In addition, you can join the “front-end afternoon tea Exchange Group” wechat group, long press to identify the following TWO-DIMENSIONAL code to add my friend, remarks add group, I pull you into the group ~