Vue偵測相關api

vm.$watch

用法: vm.$watch( expOrFn, callback, [options] ),返回值爲unwatch是一個函數用來取消觀察;下面主要理解options中的兩個參數deepimmediate以及unwatchvue

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

從上面代碼中能夠看出當immediatetrue時,就會直接進行執行回調函數數組

unwatch

實現方式是:

  1. 將被訪問到的數據dep收集到watchs實例對象上,經過this.deps存起來
  2. 將被訪問到的數據dep.id收集到watchs實例對象上,經過this.depIds存起來
  3. 最後經過watchs實例對象的teardown進行刪除
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             //參數dep是Dep實例對象
        if(!this.depIds.has(id)) {    //判斷是否存在避免重複添加
            this.depIds.add(id)       
            this.deps.push(dep)
            dep.addSub(this)         //this 是依賴
        }
    }
    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)    //將this即當前dep對象加入到watcher對象上
        }
    }
    removeSub (sub) {
        const index = this.subs.indexOf(sub)
        if(index > -1) {
            return this.subs.splice(index, 1)
        }
    }
}

分析

當執行teardown() 時須要循環;由於例如expOrFn = function () { return this.name + this.age },這時會有兩個dep分別是nameage分別都加入了watcher依賴(this),都會加入到this.deps中,因此須要循環將含有依賴的dep都刪除其依賴函數

deep

須要明白的是

  1. deep幹啥用的,例如data = {arr: [1, 2, {b: 6]},當咱們只是監聽data.arr時,在[1, 2, {b: 66}]這個數值內部發生變化時,也須要觸發,即b = 888

怎麼作呢?

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) {                    //取值
            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)) {  //判斷val是不是對象或者數組以及是否被凍結
        return
    }
    if(val._ob_) {
        const depId = val._ob_.dep.id     //能夠看前面一篇咱們對Observer類添加了this.dep = new Dep(),因此能訪問其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)
    }
}

分析

  1. window.target = this,寄存依賴
  2. let value = this.getter.call(vm, vm) 訪問當前val,並執行get

dep.depend(),若是發現val爲數組,則將依賴加入到observerdep中,也就實現了對當前數組的攔截post

  1. traverse(value) 也就是執行_traverse(val, seenObjects);核心就是對被Observerval經過val[i]經過這種操做,間接觸發get,將依賴添加到當前數值的dep中,這樣也就實現了,當內部數據發生變化,也會循環subs執行依賴的update,從而觸發回調;當是數組時,只需進行遍歷,看內部是否有Object對象便可,由於在第二步的時候,會對val進行判斷是不是數組,變改變七個方法的value,在遍歷;因此這邊只要是內部數組都會進行攔截操做,添加依賴,即對象{}這種沒沒添加依賴。
  2. seenObjects.clear()當內部因此類型數據都添加好其依賴後,就清空。
  3. window.target = undefined消除依賴

vm.$set

用法: vm.$set(target, key, value)ui

做用

  1. 對於數組,進行set則是添加新元素,並須要觸發依賴更新
  2. 對於對象,若是key值存在,則是修改value;不存在,則是添加新元素,需新元素要進行響應式處理,以及觸發更新
  3. 對於對象自己不是響應式,則直接添加key-value,無需處理
Vue.prototype.$set = function (target, key, val) {
   if(Array.isArray(target) && isValidArrayIndex(key)) {    //是數組而且key有效
        target.length = Math.max(target.length, key)   //處理key > target.length
        target.splice(key, 1, val)   //添加新元素,並輸出依賴更新同時新元素也會進行`Obsever`處理
        return val
   }
   if(key in targert && !(key in Object.prototype) {  //能遍歷而且是自身key
        target[key] = val   //觸發set,執行依賴更新
        return val
   }
   const ob = target._ob_
   if(target.isVue || (ob && ob.vm.Count) {  //不是vue實例也不是vue實例的根對象(即不是this.$data跟對象)
       //觸發警告
       return
   }
   if(!ob) {    //只添加
       target[key] = val
       return val
   }
   defineReactive(ob.value, key, val)  //進行響應式處理
   ob.dep.notify()  //觸發依賴更新
   returnv val
}

vm.$delete

用法: vm.$delete( target, key)this

做用

  1. 對於數組,進行delete則是刪除新元素,並須要觸發依賴更新
  2. 對於對象,若是key值不存在,直接return,存在,刪除元素,
  3. 對於對象自己不是響應式,則只刪除key-value,無需其餘處理
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) {  //不是vue實例也不是vue實例的根對象(即不是this.$data跟對象)
        //觸發警告
        return
   }
   if(!hasOwn(target, key)) {
       return
   }
    delete target[key]
    if(!ob) {
        return
    }
    ob.dep.notify()
}

掘金地址prototype

相關文章
相關標籤/搜索