vue2核心原理(簡易)-watch筆記

前言

  • watch 中每一個屬性都會new一個用戶watcher(new Watcher)
  • 在數據初始化得時候 開始new Watcher, Dep.target 指向此時的用戶watcher, 此時該屬性中的加入用戶watcherdep.addSub.push(watcher)
  • 當data中的數據發生變化時, 調用該數據的全部watcher
  • Watcher先將老值存起來 數據發生變化時 將新值與老值 返回給表達式(cb)

html 和 javascript 模板

<div id="app">{{ name }}</div><script src="dist/vue.js"></script><script>var vm = new Vue({data: {name: 'one'},/** 用戶watcher 幾種方式 */watch: {// name(newVal, oldVal) {//     console.log(newVal, oldVal)// },// name: [//     function(newVal, oldVal) {//         console.log(newVal, oldVal)//     },//     function(a, b) {//         console.log(a, b)//     }// ],// 'obj.n'(newVal, oldVal) {//     console.log(newVal, oldVal)// }}
    })

    vm.$mount('#app')

    vm.$watch('name', function(newValue, oldValue) {console.log(newValue, oldValue)
    })setTimeout(() => {
        vm.name = 'two'}, 2000)複製代碼

正題

$watch

export function stateMixin(Vue) {
    Vue.prototype.$watch = function(key, handler, options={}) {// 用戶建立的watcheroptions.user = truenew Watcher(this, key, handler, options)
    }
}複製代碼

options.watch 初始化開始執行

export function initState(vm) {const opts = vm.$optionsif (opts.watch) {
        initWatch(vm, opts.watch)
    }

}複製代碼
/** initWatche module *//**
 * @description 調用$watch
 */function createWatcher(vm, key, handler) {return vm.$watch(key, handler)
}/**
 * @description 初始化 watch 將觀察屬性的function拆分出來
 * @description 每一個觀察屬性 建立new watcher
 */function initWatch(vm, watch) {for (const key in watch) {const handler = watch[key]if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i])
            }
        } else {
            createWatcher(vm, key, handler)
        }
    }
}複製代碼

Watcher 類 (關鍵)

import { pushTarget, popTarget } from './dep'import { queueWatcher } from './scheduler'/** 給new Watcher一個id */let id = 0/**
 * @description 數據劫持時 指望一個屬性對應多個watcher 同時一個watcher對應多個屬性
 */class Watcher {constructor(vm, exprOrFn, cb, options) {this.vm = vmthis.exprOrFn = exprOrFnthis.cb = cbthis.options = options        this.id = id++        this.deps = []this.depsId = new Set()        /** 用戶watcher */this.user = !!options.user// 看這裏 將獲取的函數名 封裝成表達式 if (typeof exprOrFn == 'string') {this.getter = function() {// vm取值 'obj.n' -> ['obj', 'n'] -> vm['obj']['n']let path = exprOrFn.split('.')let obj = vmfor (let i = 0; i < path.length; i++) {
                    obj = obj[path[i]]
                }return obj
            }
        } else {this.getter = exprOrFn
        }/** 用戶watcher 默認取得第一次值 */this.value = this.get()
    }/** render生成vnode時 dep.push(watcher) 並更新視圖 */get() {
        pushTarget(this)//  看這裏 在VM上尋找劫持的數據 可將該屬性上push用戶Watcherconst value = this.getter.call(this.vm)
        popTarget()return value
    }update() {
         queueWatcher(this)
    }run() {/** 考慮1 渲染組件watcher *//** 考慮2 用戶watcher(newValue oldValue) */let newValue = this.get()let oldValue = this.valuethis.value = newValue/** 看這裏 用戶watcher 將值返回給cb, 並調用 */if (this.user) {this.cb.call(this.vm, newValue, oldValue)
        }
    }/** 存儲dep 並排除生成vnode時屢次調用同樣屬性 只存一個 dep 或 watcher *//** 其次當屬性發生變化時將再也不存儲dep 和 watcher */addDep(dep) {let id = dep.idif (!this.depsId.has(id)) {this.depsId.add(id)this.deps.push(dep)
            dep.addSub(this)
        }
    }

}export default Watcher複製代碼

Dep 類

/** 每一個劫持的屬性 加上惟一的標識 */let id = 0/**
 * @description 每一個劫持的屬性 new Dep
 */class Dep {constructor() {this.id = id++this.subs = []
    }/** dep傳給watcher */depend() {if (Dep.target) {
            Dep.target.addDep(this)
        }
    }addSub(watcher) {this.subs.push(watcher)
    }notify() {this.subs.forEach(watcher => watcher.update())
    }

}

Dep.target = nulllet stack = []export function pushTarget(watcher) {
    Dep.target = watcher
    stack.push(watcher)
}export function popTarget() {
    stack.pop()
    Dep.target = stack[stack.length - 1]
}export default Dep複製代碼
相關文章
相關標籤/搜索