深入Vue.js響應式原理 一、創建一個Vue應用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app'); 二、實例化一個Vue應用到底發生了什麼? v ...
深入Vue.js響應式原理
一、創建一個Vue應用
new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app');
二、實例化一個Vue應用到底發生了什麼?
- this._init()
- callHook(vm, 'beforeCreate')
- observe(vm._data)
vm._data = vm.$options.data()
proxy(vm,
_data, key) 代理到vm上訪問
function proxy(vm, _data, key)() { Object.defineProperty(target, key, { get() { return vm._data.key }, set(val) { vm._data.key = val } }) }
- callHook(vm, 'created')
- mountComponent(vm.$mount執行後執行mountComponent)
- callHook(vm, 'beforeMount')
- new Watcher(vm, updateComponent)
const updateComponent = () => { // 創建虛擬dom const vnode = vm._render() // 創建虛擬dom的過程等同於如下代碼行 // const vnode = vm.$options.render.call(vm, vm.$createElement) // 更新$el vm._update(vnode) }
- callHook(vm, 'mount')
在以上發生的行為當中,第3步與第7步兩者相輔相成;也是我們最需要關心的,弄清楚這兩者,vue響應式原理就基本掌握了
三、如何追蹤數據變化
我們都知道 數據發生變化視圖也隨之更新,那麼首先我們得知道如何監聽數據的變化
class Observer { constructor(value) { this.value = value this.walk(value) } walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } } function defineReactive(obj, key) { Object.defineProperty(obj, key, { get() { // 數據被訪問 return obj.key }, set(val) { if (val === obj.key) { return } // 數據更新了 obj.key = val } }) }
四、定義一個發佈訂閱的Dep類
當我們在創建虛擬dom的過程中,也就是執行vm.$createElement方法,可能會在多個地方使用到同一個數據欄位(如:vm.name),即多個訂閱者訂閱了name的更新,因此在Vue中定義了一個發佈訂閱的Dep類
class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } depend() { if (Dep.target) { this.addSub(Dep.target) } } notify() { this.subs.forEach(sub => sub.update()) } removeSub(sub) { const i = this.subs.findIndex(sub) if (i > -1) { this.subs.splice(i, 1) } } }
五、數據訂閱者
訂閱數據更新的到底是誰,我們先看看如下場景
<!-- 場景1 --> <div>名字:{{ userInfo.name }},全名:{{ fullName }}</div>
export default { data() { return { userInfo: { name: 'junhua', }, } }, mounted() { // 場景2 this.$watch('name', (newVal, val) => { // ... }) }, // 場景2 watch: { name(newVal, val) { // ... } }, computed() { // 場景3 fullName() { return `yang${this.userInfo.name}` } } }
從上面示例代碼看,訂閱數據更新的場景有:
- 模版插值 :
new Watcher(vm, updateComponent)
數據發生變化,更新組件 - vm.$watch : 監聽單個數據做一些邏輯操作
- computed使用場景:計算屬性
因此數據訂閱者包含一個參數expOrFn([Function|String]
),數據更新後需要執行的callback,如下:
class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.cb = cb || () => {} this.value = this.get() } get() { Dep.target = this const value = this.getter.call(this.vm, this.vm) Dep.target = undefined return value } update() { const val = this.value const newVal = this.get() this.cb.call(this.vm, newVal, val) } }
六、最終的觀察者Observer
class Observer { constructor(value) { this.value = value this.walk(value) } walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i],) } } } function defineReactive(obj, key) { const dep = new Dep() Object.defineProperty(obj, key, { get() { // 依賴收集,收集訂閱者Watcher實例 dep.depend() // 數據被訪問 return obj.key }, set(val) { if (val === obj.key) { return } // 數據更新了 obj.key = val // 通知訂閱者Watcher實例更新 dep.notify() } }) }
七、總結
我們再來回顧下實例化Vue應用的最重要的兩點
observe(vm._data) // vm.$mount() const componentUpdateWatcher = new Watcher(vm, updateComponent)
updateComponent在更新渲染組件時,會訪問1或多個數據模版插值,當訪問數據時,將通過getter攔截器把componentUpdateWatcher作為訂閱者添加到多個依賴中,每當其中一個數據有更新,將執行setter函數,對應的依賴將會通知訂閱者componentUpdateWatcher執行update,即執行updateComponent;至此Vue數據響應式目的已達到,再來看官網的這張圖片就很好理解了
github地址 文章來源:博客園-楊君華,轉載請註明出處:楊君華