I have a lot of VUE in the project, so I probably know the implementation. Recently I reviewed the code of bidirectional binding, and here is my understanding after reading it.

Project directory

After pulling the vue code, take a look at the project directory first. Since this article is about bidirectional binding, I’ll focus on bidirectional binding.

The entrance

Start with the entry: SRC /core/index.js

Index.js is simple, and the first sentence refers to Vue, let’s see what Vue is

import Vue from './instance/index'
Copy the code

Vue constructor

Came to the SRC/core/instance/index. Js

As soon as we come in, well, yes, we define the Vue constructor, and then we call several methods, and here we look at the first initMixin

import { initMixin } from './init'

function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}
initMixin(Vue)
...
export default Vue
Copy the code

Initialize the

Came to the SRC/core/instance/init. Js

This defines the _init method for the Vue constructor, which is called every time a new Vue initializes an instance.

And then you see code in the middle of _init, calling a bunch of initialization functions, and we’re just going to focus on where data goes, so let’s look at initState

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options? : Object) {
    const vm: Component = this. initState(vm) ... }}Copy the code

Came to the SRC/core/instance/state. Js initState calls the initData, initData calls to observe, then we could find down to observe

import { observe } from '.. /observer/index'
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  ...
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)}... }function initData (vm: Component) {
  let data = vm.$options.data
  ...
  observe(data, true /* asRootData */)}Copy the code

An Observer

Came to the SRC/core/observer/index. Js here, instantiate the observer object first, new observer instantiating an object, parameters for the data

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

The Observer constructor adds a value to each object and instantiates a Dep. If data is an array, we recurse. If data is an array, we walk. Walk here is defineReactive for object traversal

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()
    if (Array.isArray(value)) {
      ...
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

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

Then, let’s look at what Define Active does, and here’s the heart of the Observer. Configure the Object with Object.defineProperty, overriding get&set

Dep. Depend Add a subscriber to set: deP. Notify DeP. Dep is essentially a subscriber management center, managing all subscribers

import Dep from './dep'
export function defineReactive (obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  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
      if (Dep.target) {
        dep.depend()
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      dep.notify()
    }
  })
}
Copy the code

Dep (Subscriber Management Center)

So, at this point, the question is when the Observer get method is triggered to add a subscriber? Dep. Target = Dep. Target = Dep. Target = Dep

SRC /core/ Observer /dep.js pushTarget assigns to dep. target. Where is pushTarget called

export default class Dep {... } Dep.target =null
const targetStack = []

export function pushTarget (_target: ? Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}
Copy the code

Then, find the Watcher call pushTarget, so let’s take a look at the realization of the Watcher to SRC/core/observer/Watcher. Js here can see every time new Watcher, The get method is called and two steps are performed: pushTarget is called and getter is called to trigger the Observer get method to add itself to the subscriber

export default class Watcher {
  vm: Component;
  constructor (
    vm: Component
  ) {
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    }
    return value
  }
}
Copy the code

Then, with dep.target in place, we need to look at the dep.depend() method, so let’s go back to the Dep to see how it works. SRC /core/ Observer /dep.js calls dep.target. addDep with an instance object of deP

export default class Dep {
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)}}}Copy the code

Watcher subscriber

In SRC/core/observer/watcher. Js to this, addDep actually invoked again Dep instance addSub method, parameters are passed on watcher instance Then, we look at the addSub, So what I’m doing here is I’m pushing the Watcher instance into the SUBs array of the DEP and I’m done here and I’m adding Watcher to the DEP here in the subscriber manager here, and then deP manages the rest of the management

export default class Watcher {
  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)}}}}Copy the code

After adding the subscriber, let’s take a look at the Observer set method, which calls dep.notify

SRC /core/ Observer /dep.js calls the update method of all the watchers in subs to update the data

export default class Dep {
 notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

Here, we take a look at how Watcher updated in SRC/core/observer/Watcher. Js update calls the run method, run method with the get here to get the new value, and then put the new & old values as parameters to the cb call

export default class Watcher {
  update () {
    this.run()
  }
  
  run () {
    if (this.active) {
      const value = this.get()
      const oldValue = this.value
      this.value = value
      this.cb.call(this.vm, value, oldValue)
    }
  }
}
Copy the code

The cb is actually passed in at instantiation time, so let’s see when Watcher is instantiated and go back to initState: SRC/core/instance/state. Js initState finally also call initWatch, then createWatcher, finally $watch is instantiated Watcher object, the cb here are sent to the Watcher instances, The CB function is triggered when the monitored data changes

import Watcher from '.. /observer/watcher'
export function initState (vm: Component) {
  ...
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    createWatcher(vm, key, handler)
  }
}

function createWatcher (vm: Component, expOrFn: string | Function, handler: any, options? : Object) {
  return vm.$watch(expOrFn, handler, options)
}

Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options? : Object) :Function {
  const vm: Component = this
  const watcher = new Watcher(vm, expOrFn, cb, options)
}
Copy the code

Write in the last

Here’s the idea, in fact, is turning over the source code to go, when writing are according to their own understanding of the idea, there are problems welcome to point out ~