Following the previous chapter Vue source code parsing series (two) — how responsive systems are run inside, we understand how responsive systems are built and run, the legacy of the previous chapter is, then the whole mechanism is how to update the view, this chapter will focus on two points:
How do responsive systems collect dependencies
How do responsive systems update views
We know that data hijacking is done with Object.defineProperty. When data changes, the get method collects the dependencies, and the set method calls dep.notify to notify Watcher to call its own update method to update the view. Get, notify, update, update, update, update, update, update, update, update
get( )
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
}
Copy the code
We know that dep. target is null when we create the Watcher, and it just acts as a flag. When we create the Watcher instance, our dep. target will be assigned to the Watcher instance and put into the target stack. Here we call the pushTarget function:
// Assign the watcher instance to dep.target for dependency collection. The instance is also stored in the Target stack
export function pushTarget (_target: ? Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
Copy the code
If (dep.target); if (dep.target);
// Add yourself to the global watcher
depend () {
if (Dep.target) {
Dep.target.addDep(this)}}Copy the code
So what’s the childOb down there?
letchildOb = ! shallow && observe(val)Copy the code
We use this variable to determine if there are ob attributes under the current attribute. If there are, we continue to call dep. depend. We also need to deal with the value type currently passed in. If it is an array attribute, a dependArray is called to collect the array dependency
// Collect array dependencies
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
Copy the code
So that’s the end of collecting dependencies and now it’s time for the next trigger update
set( )
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// Determine NaN
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
We see that the set function below triggers the dep.notify() method
notify( )
// Notify all subscribers
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Copy the code
In notify we did one thing: we went through all the Watcher’s in the subs array and called the update method one by one.
update () {
if (this.lazy) {
// Calculate the properties in this code block
// Here we assign dirty to true
// Do not read the value immediately
// When the render-watcher update is triggered
// Re-render the page, and the calculated properties are re-read
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)}}Copy the code
So what does the update method implement? What’s lazy, dirty, sync?
if (options) { this.deep = !! options.deep this.user = !! options.user this.lazy = !! options.lazy this.sync = !! options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for Batching this.active = true // Dirty = true this.dirty = this.lazy // for lazy watchersCopy the code
That controls the computed properties, so when the render — Watcher update method is called, this.dirty will be true and will recalculate computed values and render views, which we won’t describe here. So let’s look directly at queueWatcher() :
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if(! flushing) { queue.push(watcher) }else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1.0, watcher)
}
// queue the flush
if(! waiting) { waiting =true
nextTick(flushSchedulerQueue)
}
}
}
Copy the code
We can see an update queue that points to:
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, 'updated')}}}Copy the code
The updated hook for our callback is a little too small. We call initRender to create the DOM and nextTick, which will be covered later. In the next chapter we will implement a two-way binding that will astonish interviewers. Vue source code parsing series (4) – to achieve a two-way binding bar (blow the interviewer)