Response principle

When Vue initializes data, it redefines setters and getters for all properties in the data using object.defineProperty. When the page gets the corresponding property, The GET method is triggered and a dependency collection is performed (the watcher of the current component is collected). If the property changes, the dependencies are notified and the corresponding Watcher is triggered to update.

What is dependent collection?

Object. DefineProperty is used to intercept the data property when it is redefined and then render it. So the logical thing to do before the actual rendering is to collect the dependencies. It says that when the dependencies are collected, a watcher is created for each property, and if the property changes, the corresponding watcher is notified to update the view.

Let's look at the source code

Before looking at the source code, first consider a problem, every day we write Vue, know that it has a process from creation to destruction, the official point, that is the Vue life cycle, okey!

[Look through the water]

New Vue instance – initialize data,events – Template compile – Instance mount – Instance update – Instance destroy, we start with new Vue() instance

Source code analysis

1, what does new Vue() do?

code position : src/core/instance/index.js

// Import a series of modules
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '.. /util/index'

function Vue (options) {
  // We call the _init method of the Vue constructor and pass in the options parameter
  this._init(options)
}

initMixin(Vue)  // 1. Initialize each _init method, including initialization options, render, events, beforCreated, etc
stateMixin(Vue) // 2. Use object.defineProperty to create responsive data and initialize $set $delete $watch
eventsMixin(Vue) // 3. Initialize $on $EMIT in vUE
lifecycleMixin(Vue) // 4. Initialize the life cycle
renderMixin(Vue) // 5. Initialize the _render method

export default Vue

Copy the code

Function Vue (options) {… } is the only code that can be used to do this._init(options), then a series of various initialization method calls, and finally export the Vue instance.

So where is this._init(options) defined? In the initMixin module

code position : src/core/instance/init.js

export function initMixin (Vue) {
 // Mount the Vue instance prototype directly
  Vue.prototype._init = function (options) {
    const vm = this
    / / merge options
    vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
    )
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) 
    initState(vm)
    initProvide(vm) 
    callHook(vm, 'created')
    
    // Mount the instance using the $mount method
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

Init.js does a couple of things: merge the options that the user initializes the instance with the options constructor, then call a series of module initialization methods, and finally mount the instance via $mount.

Back to our topic, reactive data principles, which is how the initialization Vue data is rendered and the listener is set up, so let’s focus on the stateMixin module, okay

2. Initialize data

Entering the stateMixin module, we look directly at the initData function, which initializes the data property

// vm: constructor root instance
function initData (vm: Component) { 
  // Retrieve the data passed in by the user
  let data = vm.$options.data 
  // Template syntax is different from standard syntax
  data = vm._data = typeof data === 'function' 
    ? getData(data, vm)
    : data || {}
  if(! isPlainObject(data)) { data = {} }// proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if(process.env.NODE_ENV ! = ='production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if(! isReserved(key)) { proxy(vm,`_data`, key) 
    }
  }
  /* es6: props, methods, functions, functions, functions, functions, functions, functions, functions, functions, functions DefineProperty includes all the functions of Object. DefineProperty, but the important point is to solve the problem of not listening to arrays, objects, etc. * /
  
  // Observe data
  observe(data, true /* asRootData */)}Copy the code

Observe (data, true /* asRootData */) observe(data, true /* asRootData */)

3. Create observations

The obsrver method does not do much. The obsrver method does not do much. The obsrver method does not do much.

Code position: SRC/core/observer/index, js 113 rows

export function observe (value: any, asRootData: ? boolean) :Observer | void {
  if(! isObject(value) || valueinstanceof VNode) {
    Data () {template syntax returns an object return {}} new Vue ({data is also an object data:{}}) */
    return
  }
  let ob: Observer | void
  // The listener will not be listened to again
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 
    ob = value.__b__
  } else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) && ! value._isVue ) {// Focus, focus, focus. Create a listener for an object that is not being listened on
    ob = new Observer(value) 
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

Copy the code

Follow it: new Observer() creates a listener. Note that these are two things: the Observer method and the Observer class

Code position: SRC/core/observer/index, js line 37

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)
 /* Array (array); Is by rewriting the array prototype method, including push,shift,unshift,pop,splice, sort, etc. In other words, vue listens for array changes by rewriting the prototype method + recursive traversal to observe the data. We will explain in detail */
    if (Array.isArray(value)) { / / array
      if (hasProto) {
        protoAugment(value, arrayMethods) // Rewrite the array prototype method
      } else {
        copyAugment(value, arrayMethods, arrayKeys) // Copy the existing methods of the array
      }
      this.observeArray(value) ObserveArray looks down at the method entities
    } else { 
      this.walk(value) // Redefine the object type}}// walk: if it is an object, walk into this method
  walk (obj: Object) { 
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) { 
     // defineReactive data. You can see the defineReactive method here and the method entities belowdefineReactive(obj, keys[i]); }}// If it is an array, iterate over the array, go to the observer method to see if it is listening, and continue the recursive callback if it is not listening
  observeArray (items: Array<any>) { 
    for (let i = 0, l = items.length; i < l; i++) {
      // Observe each item in the array
      observe(items[i])   
    }
  }
}
Copy the code

From the above code, we can see that one of the main things that the Observer class does is to distinguish between arrays and objects, walk through them, and then execute the defineReactive method, Anyway, we’re going to end up with the defineReactive method and then we’re going to look at the key method of defineReactive responsive data binding which is where we often talk about object.defineProperty () being applied.

4. Data hijacking

Code position: SRC/core/observer/index, js 148 rows

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {
 // Create a dependent collector - concrete Dep class entity 30 seconds down
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if((! getter || setter) &&arguments.length === 2) {
    val = obj[key]
  }
  // If it is an array, observe recursively
  letchildOb = ! shallow && observe(val)// Point, point, point
  Object.defineProperty(obj, key, { 
    enumerable: true.configurable: true.Get => dep.depend()
    get: function reactiveGetter () { 
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // Collect dependencies on watcher, that is, create observers
        dep.depend()  
        if (childOb) {
          childOb.dep.depend() 
          if (Array.isArray(value)) {
	    // Collect arrays recursively
            dependArray(value)
          }
        }
      }
      return value
    },
    // Description property set => dep.notify()
    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()
      }
      // #7981: for accessor properties without setter
      if(getter && ! setter)return
      if (setter) {
        setter.call(obj, newVal)
      } else{ val = newVal } childOb = ! shallow && observe(newVal)// Trigger the data corresponding to the dependency update, focus, focus, see below
      dep.notify() 
    }
  })
}

Copy the code
4-1 Dep subscriber

When creating an observation, create a Dep collector for that component const Dep = new Dep(), Create its own watcher observer (in the get of the object.defineProperty method) for each data attribute by dep.depend(), then the set method that triggers the data update calls dep.notify() to trigger the view update

Here’s an important point: a component has only one DEP, but there are multiple Wathcers: Go Go Go

The Dep class can see the constructor, add watcher, remove watcher, etc., so let’s look at the Dep collector and update method notify().

Code position: SRC /core/observer/dep.js 13 line

export default class Dep {
  statictarget: ? Watcher; id: number; subs:Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)}}// Notify the store of dependency updates
  notify () { 
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      // The update method corresponding to the modified attribute in the dependency
      subs[i].update() 
    }
  }
}
Copy the code

The wachter observer class defines the wachter observer class. The wachter observer class defines the wachter observer class. The wachter observer class defines the wachter observer class.

5, update the view

The update () method in the SRC/core/observer/wachter js 166 lines, main is to modify the data, and then drive the view, don’t do further analysis, here share here because it is over, you should understand the reactive principle of vue;

Note: The update method involves another asynchronous queue issue, the API nextTick. See a later article.

Welcome to like, a little encouragement, a lot of growth

A link to the

  • Front-end visualization platform implementation scheme
  • Vue3 10 minutes takes you seconds to vue3
  • Vue template compilation principle
  • The vue constructor extends
  • How does VUEX state management work
  • Vue-router source analysis principle
  • Vue [how to listen for array changes]
  • Handwritten Vue response [object.defineProperty]
  • Vue responsive source sharing