Vue Detects Related api

Keywords: Javascript Vue

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:

  1. Collect the accessed data dep onto the watchs instance object and store it through this.deps
  2. Collect the accessed data dep.id onto the watchs instance object and store it through this.depIds
  3. 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

  1. 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

  1. window.target = this, register dependency
  2. 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

  1. 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.
  2. seenObjects.clear() is emptied when all type data has its dependencies added internally.
  3. window.target = undefined to eliminate dependencies

vm.$set

Usage: vm.$set(target, key, value)

Effect

  1. For arrays, set ting adds new elements and triggers dependent updates
  2. 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
  3. 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

  1. For arrays, deleting deletes deletes new elements and triggers dependent updates
  2. For objects, if the key value does not exist, return directly, exist, delete the element,
  3. 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()
}

Nugget address

Posted by theycallmepj on Tue, 21 May 2019 09:18:55 -0700