directory
Vm.$watch Internal principle 4. Implementation principle of the deep parameter 5. Initialize watch 6. Summary
1. Service scenarios
Watch is used to monitor the change of some data, and then call what function to process it. A single piece of data can affect multiple pieces of data, such as browser adaptation, monitoring routing objects, monitoring its own property changes, and so on.
2. Usage
vm.$watch( expOrFn, callback, [options] )
Definition: Observe a change in the evaluation result of an expression or function on a Vue instance. The callback takes new and old values. The expression accepts only supervised key paths. For more complex expressions, replace them with a function. Example:
// key path VM.$watch('a.b.c'.function(newVal, oldVal) {// do something}) // function VM$watch(
function() {// The expression 'this.a + this.b' is called each time it yields a different result. // This is like listening on an undefined computed propertyreturn this.a + this.b
},
function(newVal, oldVal) {// do something})Copy the code
Option deep: To detect changes in the internal value of an object, you can specify deep: true in the option argument. Note that you don’t need to do this to listen for changes in the array.
vm.$watch('someObject', callback, {
deep: true
})
vm.someObject.nestedValue = 123
// callback is fired
Copy the code
Option immediate: Specifying immediate: true in the option argument triggers the callback immediately with the current value of the expression.
vm.$watch('a', callback, {
immediate: true}) // Trigger the callback immediately with the current value of 'a'Copy the code
Return value: vm.$watch returns a function that cancels this listening.
var unwatch = vm.$watch(
'value'.function () {
doSomething()
if (unwatch) {
unwatch()
}
},
{ immediate: true})Copy the code
3. Vm.$watch internal principle
Vue change detection vUE change detection vUE change detection vUE change detection vUE change detection
The vm.$watch parameter “deep” and “immediate” is not available. Let’s see how vm.$watch is implemented:
Vue.prototype.$watch=function(exp,cb,options){
const vm=this
options=options||{}
const watcher=new Watcher(vm,exp,cb,options)
if(options.immediate){
cb.call(vm,watcher.value)
}
return function unwatchFn(){
watcher.teardown()
}
}
Copy the code
The logic is simple, execute new Watcher to implement the basic function of vm.$watch. But there is one detail: exp accepts functions, so the Watcher constructor needs to be changed.
exportDefault class watcher{constructor(vm,exp,cb){this.vm=vmif(typeof exp==='function'){
this.getter=exp
}else{
this.getter=parsePath(exp)
}
this.cb=cb
this.value=this.get()
}
......
}
Copy the code
When exp is a function, it not only returns data dynamically, but all data it reads is observed by Watcher. Wacther will be notified when any of them change.
Return value: After executing vm.$watch, return a function unwatchFn to cancel the observation. To cancel the watch, we need to know who Watcher itself subscribes to, which DEPs the Watcher instance collects. Loop through the DEPS it collects and remove its dependencies from the Dep.
Add the addDep method to Watcher to record which DEPs you subscribe to:
exportConstructor (vm,exp,cb){constructor(vm,exp,cb){this.vm=vm // add this.deps=[] / add this.depids =new Set()if(typeof exp==='function'){
this.getter=exp
}else{this.getter=parsePath(exp)} this.cb=cb this.value=this.get()} // Add addDep(dep){const id=dep.id // dep watcher generates correlationif(! This.depids.has [id]){this.depids.add (id) //watcher add dep this.depids.push (dep) //dep add watcher depids.addSub (this)}}...... }Copy the code
Code analysis: We use depIds to determine that the current Watcher has subscribed to Dep and will not re-subscribe. When Watcher reads the value, the collection dependency logic is triggered. Add (id) to record that Watcher has subscribed to the Dep, push(Dep) to record which DEPs watcher has subscribed to, and finally trigger dep.addSub(this) to add himself to the Dep.
With addDep added to Watcher, the logic for collecting dependencies in Dep also needs to change:
letUid = 0 / / newexport default class Dep{
constructor(){
this.id=uid++ //新增
this.subs=[]
}
depend() {if(window.target){this.addSub(window.target) // Discard window.target.adddep (this) // add}}}Copy the code
Code analysis: At this point, the Dep records which Watcher needs to be notified when the data changes, and the Watcher records which DEPS it has been notified by. So Dep and Watcher are many-to-many
Why this is many-to-many: We know that when a view uses the same data multiple times, one Dep corresponds to multiple Watchers. Also, when a watcher looks at a function that uses multiple data, the Watcher collects multiple DEPs.
Unwatcher: Now that we know which dePs Watcher subscribed to, we can add a teardown method in Watcher to notify those subscriptions and remove them from the dependency list:
// Remove yourself from the DEP listteardown() {let i=this.deps.length
while(i--){
this.deps[i].removeSub(this)
}
}
Copy the code
Add removeSub method to deP:
removeSub(sub){
const index=this.subs.indexOf(sub)
if(index>-1){
return this.subs.splice(index,1)
}
}
Copy the code
This is how Unwatch works. When data changes, watcher is not notified that it has been deleted.
4deep parameter principle implementation
Deep: deep: deep: deep: deep: deep: deep: deep: deep: deep
exportDefault class constructor {constructor(vm,exp,cb){this.vm=vm // addif(options){ this.deep=!! options.deep }else{
this.deep=false
}
this.deps=[]
this.depIds=new Set()
if(typeof exp==='function'){
this.getter=exp
}else{
this.getter=parsePath(exp)
}
this.cb=cb
this.value=this.get()
}
get(){
window.target=this
letCall (value = this. Getter. This vm, enclosing the vm) / / newif(this.deep){
traverse(value)
}
window.target=undefined
return value
}
}
Copy the code
If the user uses the deep argument, traverse will be called before window.target=undefined to handle the deep logic. Otherwise, Watcher would not collect the dependency list of subvalues.
Traverse function: This function is very simple. It’s a recursive way of recursing all the subvalues of value to trigger them to collect dependencies.
5. Initialize watch
Type: {[key: string] : string | Function | Object | Array}
An object whose key is the expression to observe and whose value is the corresponding callback function. The value can also be a method name, or an object that contains options. The Vue instance will call $watch() at instantiation time, iterating through each property of the Watch object.
Example:
var vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5
}
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', oldVal, oldVal)},'someMethod'// This callback is called when the property of any object being listened on changes, no matter how deep it is nested c: {handler:function (val, oldVal) { /* ... */ },
deep: true}, // This callback will be called immediately after listening starts d: {handler:'someMethod',
immediate: true
},
e: [
'handle1'.function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
// watch vm.e.f's value: {g: 5} 'e.f': function (val, oldVal) { /* ... */ } } }) vm.a = 2 // => new: 2, old: 1Copy the code
$watch = vm.$watch = vm.$watch = vm.$watch = vm.
But the value of the Watch option supports strings, functions, arrays, and objects. Different types have different uses, so we need to do some adaptation when calling vm.$watch.
function initWatch(vm,watch){
for(const key in watch){
const handler=watch[key]
if(Array.isArray(handler)){
for(leti=0; i<handler.length; i++){ createWatcher(vm,key,handler[i]) } }else{
createWatcher(vm,key,handler)
}
}
}
Copy the code
Code analysis: First divide watch option values into two classes, array and others. The createWatcher function is then called to handle the other types and vm.$watch is called to create the observation expression.
function createWatcher(vm,exp,handler,options){
if(isPlainObject(handler)){
options=handler
handler=handler.handler
}
if(typeof handler==='string'){
handler=vm[handler]
}
return vm.$watch(exp,handler,options)
}
Copy the code
Code analysis: Three types are handled:
- Function: Pass directly to vm.$watch without special handling
- String: Takes the method from the VM and assigns it to handler
- The options value is set to handler and the variable handler is set to handler object handler method.
That’s it for watch initialization.
6. Summary
In fact, watch is not very difficult, mainly because Watcher class and Dep class are flexibly used.
One API at a time, one day at a time.