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 ~