vm.$watch
Usage: vm. $watch (expOrFn, callback, [options]), the return value of unwatch is a function to cancel observation; the following main understanding is the two parameters deep and immediate in options and unwatch
Vue.prototype.$watch = function (expOrFn, cb, options) { const vm = this options = options || {} const watcher = new Watcher(vm, expOrFn, cb, options) if(options.immediate) { cb.call(vm, watcher,.value) } return function unwatchFn() { watcher.teardown() } }
immediate
You can see from the code above that when immediate is true, the callback function is executed directly
unwatch
This is achieved by:
- Collect the accessed data dep onto the watchs instance object and store it through this.deps
- Collect the accessed data dep.id onto the watchs instance object and store it through this.depIds
- Last deleted by teardown of watchs instance object
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function') { this.getter = expOrFn }else { this.getter = parsePath(expOrFn) } this.cb = cb this.value = this.get() } .... addDep (dep) { const id = dep.id //The parameter dep is a Dep instance object if(!this.depIds.has(id)) { //Determine if there is a way to avoid duplicate additions this.depIds.add(id) this.deps.push(dep) dep.addSub(this) //this is dependent } } teardown () { let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } } } let uid = 0 class Dep { constructor () { this.id = uid++ ... } ... depend () { if(window.target) { window.target.addDep(this) //Add this, the current dep object, to the watcher object } } removeSub (sub) { const index = this.subs.indexOf(sub) if(index > -1) { return this.subs.splice(index, 1) } } }
Analysis
A loop is required when teardown() is executed; because, for example, expOrFn = function () {return this.name + this.age}, two DEPs are added to the watcher dependency (this) by name and age, respectively, and both are added to this.deps, so a loop is required to remove all DEPs that have dependencies from their dependencies
deep
What you need to understand is
- What does deep do, such as data = {arr: [1, 2, {b: 6]}, need to be triggered when we're just listening on data.arr and there's an internal change in this number [1, 2, {b: 66}], that is, b = 888
What to do?
class Watcher { constructor (vm, expOrFn, cb, options) { this.vm = vm this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function') { this.getter = expOrFn }else { this.getter = parsePath(expOrFn) } if(options) { //Value this.deep = !!options.deep }else { this.deep = false } this.cb = cb this.value = this.get() } get () { window.target = this let value = this.getter.call(vm, vm) if(this.deep) { traverse(value) } window.target = undefined return value } ... } const seenObjects = new Set() function traverse (val) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse(val, seen) { let i, keys const isA = Array.isArray(val) if((!isA && isObject(val)) || Object.isFrozen(val)) { //Determine if Vals are objects or arrays and frozen? return } if(val._ob_) { const depId = val._ob_.dep.id //As you can see in the previous article, we added this.dep = new Dep() to the Observer class so we can access its dep.id if(seen.has(depId)) { return } seen.add(depId) } if(isA) { i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[i], seen) } }
Analysis
- window.target = this, register dependency
- let value = this.getter.call(vm, vm) accesses the current val and executes get
dep.depend(), if the val ue is found to be an array, then the dependency is added to the observer's dep, which implements the interception of the current array
- traverse(value) is the execution of _traverse(val, seenObjects); the core is to indirectly trigger get through val[i] on the value of Observer, adding dependencies to the dep of the current value, which is also achieved. When the internal data changes, the subs also loops to perform dependent update s, triggering callbacks; when it is an array, simply traverse to see insideWhether there is an Object object or not, because in the second step, the value is judged as an array, changing the value of the seven methods, and traversing; so any internal array on this side will intercept and add a dependency, that is, object {} without adding a dependency.
- seenObjects.clear() is emptied when all type data has its dependencies added internally.
- window.target = undefined to eliminate dependencies
vm.$set
Usage: vm.$set(target, key, value)
Effect
- For arrays, set ting adds new elements and triggers dependent updates
- For an object, if the key value exists, it modifies the value; if it does not, it adds a new element, which needs to be processed responsively, and triggers an update
- If the object itself is not responsive, add key-value directly without processing
Vue.prototype.$set = function (target, key, val) { if(Array.isArray(target) && isValidArrayIndex(key)) { //Is an array and key is valid target.length = Math.max(target.length, key) //Processing key > target.length target.splice(key, 1, val) //Add new elements and output dependent updates with `Obsever`processing return val } if(key in targert && !(key in Object.prototype) { //Traversable and self-key target[key] = val //Trigger set to perform dependent updates return val } const ob = target._ob_ if(target.isVue || (ob && ob.vm.Count) { //Not a vue instance nor the root object of a vue instance (that is, not this.$data and object) //Trigger warning return } if(!ob) { //Add only target[key] = val return val } defineReactive(ob.value, key, val) //Conduct responsive processing ob.dep.notify() //Trigger Dependent Updates returnv val }
vm.$delete
Usage: vm. $delete (target, key)
Effect
- For arrays, deleting deletes deletes new elements and triggers dependent updates
- For objects, if the key value does not exist, return directly, exist, delete the element,
- If the object itself is not responsive, only the key-value is deleted and no additional processing is required
Vue.prototype.$delete = function (target, key) { if(Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = target._ob_ if(target.isVue || (ob && ob.vm.Count) { //Not a vue instance nor the root object of a vue instance (that is, not this.$data and object) //Trigger warning return } if(!hasOwn(target, key)) { return } delete target[key] if(!ob) { return } ob.dep.notify() }