vue的響應式是如何實現的?
Watcher ----- Dep ---- walk + defineProperty
1 vue 初始化 -- 進行數據的set、get綁定,並創建了一個Dep對象
// src > core > observer > index.js
// 執行 new Vue 時會依次執行以下方法 // 1. Vue.prototype._init(option) // 2. initState(vm) // 3. observe(vm._data) // 4. new Observer(data) // 5. 調用 walk 方法,遍歷 data 中的每一個屬性,監聽數據的變化。
// 遍歷所有屬性並將它們轉換為getter/setter。此方法僅在值類型為Object時調用。
function walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } // 6. 執行 defineProperty 監聽數據讀取和設置。
// 定義對象上的響應式屬性
function defineReactive(obj, key, val) { // 為每個屬性創建 Dep(依賴搜集的容器) const dep = new Dep(); // 綁定 get、set Object.defineProperty(obj, key, { get() { const value = val; // 如果有 target 標識,則進行依賴搜集 if (Dep.target) { dep.depend(); } return value; }, set(newVal) { val = newVal; // 修改數據時,通知頁面重新渲染 dep.notify(); }, });
Dep對象是什么?
1.2 Dep對象 -- 用於依賴收集,它實現了一個發布訂閱模式,完成了數據Data和渲染視圖 Watcher 的訂閱
// src > core > observer > dep.js
class Dep { // Dep.target 是一個 Watcher 類型。 static target: ?Watcher; // subs 存放搜集到的 Watcher 對象集合 subs: Array<Watcher>; constructor() { this.subs = []; } addSub(sub: Watcher) { // 搜集所有使用到這個 data 的 Watcher 對象。 this.subs.push(sub); } depend() { if (Dep.target) { // 搜集依賴,最終會調用上面的 addSub 方法 Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { // 調用對應的 Watcher,更新視圖 subs[i].update(); } } }
Watch的功能是什么?
1.3 Watch 觀察者 -- 實現了渲染方法 _render 和 Dep 的關聯, 初始化 Watcher 的時候,打上 Dep.target 標識,然后調用 get 方法進行頁面渲染。
// src > core > observer > watcher.js
class Watcher { constructor(vm: Component, expOrFn: string | Function) { // 將 vm._render 方法賦值給 getter。 // 這里的 expOrFn 其實就是 vm._render。 this.getter = expOrFn; this.value = this.get(); } get() { // 給 Dep.target 賦值為當前 Watcher 對象 Dep.target = this; // this.getter 其實就是 vm._render // vm._render 用來生成虛擬 dom、執行 dom-diff、更新真實 dom。 const value = this.getter.call(this.vm, this.vm); return value; } addDep(dep: Dep) { // 將當前的 Watcher 添加到 Dep 收集池中 dep.addSub(this); } update() { // 開啟異步隊列,批量更新 Watcher queueWatcher(this); } run() { // 和初始化一樣,會調用 get 方法,更新視圖 const value = this.get(); } }
小結:Vue 通過 defineProperty 完成了 Data 中所有數據的代理,當數據觸發 get 查詢時,會將當前的 Watcher 對象加入到依賴收集池 Dep 中,當數據 Data 變化時,會觸發 set 通知所有使用到這個 Data 的 Watcher 對象去 update 視圖。
Watcher 是什么時候創建的?
2 模板渲染
2.1 new Vue 執行流程
// src > core > instance > lifecycle.js
// 1. Vue.prototype._init(option) // 2. vm.$mount(vm.$options.el) // 3. render = compileToFunctions(template) ,編譯 Vue 中的 template 模板,生成 render 方法。 // 4. Vue.prototype.$mount 調用上面的 render 方法掛載 dom。 // 5. mountComponent // 6. 創建 Watcher 實例 const updateComponent = () => { vm._update(vm._render()); }; // 結合上文,我們就能得出,updateComponent 就是傳入 Watcher 內部的 getter 方法。 new Watcher(vm, updateComponent); // 7. new Watcher 會執行 Watcher.get 方法 // 8. Watcher.get 會執行 this.getter.call(vm, vm) ,也就是執行 updateComponent 方法 // 9. updateComponent 會執行 vm._update(vm._render()) // 10. 調用 vm._render 生成虛擬 dom Vue.prototype._render = function (): VNode { const vm: Component = this; const { render } = vm.$options; let vnode = render.call(vm._renderProxy, vm.$createElement); return vnode; }; // 11. 調用 vm._update(vnode) 渲染虛擬 dom Vue.prototype._update = function (vnode: VNode) { const vm: Component = this; if (!prevVnode) { // 初次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { // 更新 vm.$el = vm.__patch__(prevVnode, vnode); } }; // 12. vm.__patch__ 方法就是做的 dom diff 比較,然后更新 dom,這里就不展開了。
Watcher 是在 Vue 初始化的階段創建的,屬於生命周期中 beforeMount 的位置創建的,創建 Watcher 時會執行 render 方法,最終將 Vue 代碼渲染成真實的 DOM。
Vue初始化到渲染dom的過程:
當數據發生改變時,vue是怎樣進行更新的?
由上圖可見,在 Data 變化時,會調用 Dep.notify 方法,隨即調用 Watcher 內部的 update 方法,此方法會將所有使用到這個 Data 的 Watcher 加入一個隊列,並開啟一個異步隊列進行更新,最終執行 _render 方法完成頁面更新。
整體流程如下:
總結:Vue的響應式原理
- 從 new Vue 開始,首先通過 get、set 監聽 Data 中的數據變化,同時創建 Dep 用來搜集使用該 Data 的 Watcher。
- 編譯模板,創建 Watcher,並將 Dep.target 標識為當前 Watcher。
- 編譯模板時,如果使用到了 Data 中的數據,就會觸發 Data 的 get 方法,然后調用 Dep.addSub 將 Watcher 搜集起來。
- 數據更新時,會觸發 Data 的 set 方法,然后調用 Dep.notify 通知所有使用到該 Data 的 Watcher 去更新 DOM。