Vue響應式原理最佳解答
阿新 • • 發佈:2021-07-14
看過vue官方文件的同學,對這張圖應該已然相當熟悉了。
vue的響應式是如何實現的?
聽過太多回答,通過Object.defineProperty
,可是再詳細的問時,對方渾然不知。
先擼為敬
const Observer = function(data) { // 迴圈修改為每個屬性新增get set for (let key in data) { defineReactive(data, key); } } const defineReactive = function(obj, key) { // 區域性變數dep,用於get set內部呼叫 const dep = new Dep(); // 獲取當前值 let val = obj[key]; Object.defineProperty(obj, key, { // 設定當前描述屬性為可被迴圈 enumerable: true, // 設定當前描述屬性可被修改 configurable: true, get() { console.log('in get'); // 呼叫依賴收集器中的addSub,用於收集當前屬性與Watcher中的依賴關係 dep.depend(); return val; }, set(newVal) { if (newVal === val) { return; } val = newVal; // 當值發生變更時,通知依賴收集器,更新每個需要更新的Watcher, // 這裡每個需要更新通過什麼斷定?dep.subs dep.notify(); } }); } const observe = function(data) { return new Observer(data); } const Vue = function(options) { const self = this; // 將data賦值給this._data,原始碼這部分用的Proxy所以我們用最簡單的方式臨時實現 if (options && typeof options.data === 'function') { this._data = options.data.apply(this); } // 掛載函式 this.mount = function() { new Watcher(self, self.render); } // 渲染函式 this.render = function() { with(self) { _data.text; } } // 監聽this._data observe(this._data); } const Watcher = function(vm, fn) { const self = this; this.vm = vm; // 將當前Dep.target指向自己 Dep.target = this; // 向Dep方法添加當前Wathcer this.addDep = function(dep) { dep.addSub(self); } // 更新方法,用於觸發vm._render this.update = function() { console.log('in watcher update'); fn(); } // 這裡會首次呼叫vm._render,從而觸發text的get // 從而將當前的Wathcer與Dep關聯起來 this.value = fn(); // 這裡清空了Dep.target,為了防止notify觸發時,不停的繫結Watcher與Dep, // 造成程式碼死迴圈 Dep.target = null; } const Dep = function() { const self = this; // 收集目標 this.target = null; // 儲存收集器中需要通知的Watcher this.subs = []; // 當有目標時,繫結Dep與Wathcer的關係 this.depend = function() { if (Dep.target) { // 這裡其實可以直接寫self.addSub(Dep.target), // 沒有這麼寫因為想還原原始碼的過程。 Dep.target.addDep(self); } } // 為當前收集器新增Watcher this.addSub = function(watcher) { self.subs.push(watcher); } // 通知收集器中所的所有Wathcer,呼叫其update方法 this.notify = function() { for (let i = 0; i < self.subs.length; i += 1) { self.subs[i].update(); } } } const vue = new Vue({ data() { return { text: 'hello world' }; } }) vue.mount(); // in get vue._data.text = '123'; // in watcher update /n in get
這裡我們用不到100行的程式碼,實現了一個簡易的vue響應式。當然,這裡如果不考慮期間的過程,我相信,40行程式碼之內可以搞定。但是我這裡不想省略,為什麼呢?我怕你把其中的過程自動忽略掉,怕別人問你相關東西的時候,明明自己看過了,卻被懟的啞口無言。總之,我是為了你好,多喝熱水。
Dep的作用是什麼?
依賴收集器,這不是官方的名字蛤,我自己起的,為了好記。
用兩個例子來看看依賴收集器的作用吧。
-
例子1,毫無意義的渲染是不是沒必要?
const vm = new Vue({ data() { return { text: 'hello world', text2: 'hey', } } })
當
vm.text2
的值發生變化時,會再次呼叫render
,而template
中卻沒有使用text2
,所以這裡處理render
是不是毫無意義?針對這個例子還記得我們上面模擬實現的沒,在
Vue
的render
函式中,我們呼叫了本次渲染相關的值,所以,與渲染無關的值,並不會觸發get
,也就不會在依賴收集器中新增到監聽(addSub
方法不會觸發),即使呼叫set
賦值,notify
中的subs
也是空的。OK,繼續迴歸demo,來一小波測試去印證下我說的吧。const vue = new Vue({ data() { return { text: 'hello world', text2: 'hey' }; } }) vue.mount(); // in get vue._data.text = '456'; // in watcher update /n in get vue._data.text2 = '123'; // nothing
-
例子2,多個Vue例項引用同一個data時,通知誰?是不是應該倆都通知?
let commonData = { text: 'hello world' }; const vm1 = new Vue({ data() { return commonData; } }) const vm2 = new Vue({ data() { return commonData; } }) vm1.mount(); // in get vm2.mount(); // in get commonData.text = 'hey' // 輸出了兩次 in watcher update /n in get
希望通過這兩個例子,你已經大概清楚了Dep
的作用,有沒有原來就那麼回事的感覺?有就對了。總結一下吧(以下依賴收集器實為Dep
):
vue
將data
初始化為一個Observer
並對物件中的每個值,重寫了其中的get
、set
,data
中的每個key
,都有一個獨立的依賴收集器。- 在
get
中,向依賴收集器添加了監聽 - 在mount時,例項了一個
Watcher
,將收集器的目標指向了當前Watcher
- 在
data
值發生變更時,觸發set
,觸發了依賴收集器中的所有監聽的更新,來觸發Watcher.update
為了幫助大家更好溫習重點知識、更高效的準備面試,特別整理了《Vue 面試題總結》電子稿檔案。
vue-cli工程
- 構建的 vue-cli 工程都到了哪些技術,它們的作用分別是什麼?
- vue-cli 工程常用的 npm 命令有哪些?
- 請說出vue-cli工程中資料夾和檔案的用處
- config資料夾 下 index.js 的對於工程 開發環境 和 生產環境 的配置
- 請你詳細介紹一些 package.json 裡面的配置
vue核心知識點
- 對於Vue是一套漸進式框架的理解
- vue.js的兩個核心是什麼?
- 請問 v-if 和 v-show 有什麼區別
- vue常用的修飾符
- v-on可以監聽多個方法嗎?
- vue中 key 值的作用
- vue-cli工程升級vue版本
- vue事件中如何使用event物件?
- $nextTick的使用
- Vue 元件中 data 為什麼必須是函式
- v-for 與 v-if 的優先順序
- vue中子元件呼叫父元件的方法
- vue中 keep-alive 元件的作用
- vue中如何編寫可複用的元件?
- 什麼是vue生命週期?
- vue生命週期鉤子函式有哪些?
- vue如何監聽鍵盤事件中的按鍵?
- vue更新陣列時觸發檢視更新的方法
- vue中物件更改檢測的注意事項
- 解決非工程化專案初始化頁面閃動問題
- v-for產生的列表,實現active的切換
- v-model語法糖的元件中的使用
- vue中自定義過濾器
- vue等單頁面應用及其優缺點
- 什麼是vue的計算屬性?
- vue-cli提供的幾種腳手架模板
- vue父元件如何向子元件中傳遞資料?
- vue彈窗後如何禁止滾動條滾動?
- 計算屬性的快取和方法呼叫的區別
- vue-cli中自定義指令的使用
vue-router
- vue-router如何響應 路由引數 的變化?
- 完整的 vue-router 導航解析流程
- vue-router有哪幾種導航鉤子( 導航守衛 )?
- vue-router傳遞引數的幾種方式
- vue-router的動態路由匹配
- vue-router如何定義巢狀路由?
- 元件及其屬性
- vue-router實現路由懶載入( 動態載入路由 )
- vue-router路由的兩種模式
- history路由模式配置及後臺配置
vuex
- 什麼是vuex?
- 使用vuex的核心概念
- vuex在vue-cli中的應用
- 在vue中使用vuex,修改state的值
- vuex actions非同步修改狀態
http請求
- Promise物件是什麼?
- axios、fetch與ajax有什麼區別?
- 什麼是JS的同源策略和跨域問題?
- 如何解決跨域問題?
- axios有什麼特點?
UI樣式
- .vue元件的scoped屬性的作用
- 如何讓CSS只在當前元件中起作用?
- vue-cli中常用的UI元件庫
- 如何適配移動端?【 經典 】
- 移動端媒體查詢
- vue內容垂直和水平居中
- vue-cli引入圖片的方法
- 移動端常見樣式問題
- 文字超出隱藏
常用功能
- vue中如何實現tab切換功能?
- vue中keep-alive 實現標籤頁元件快取
- vue中實現頁面從右往左側滑入效果
- vue中父子元件如何相互呼叫方法?
- vue中央事件匯流排的使用
MVVM設計模式
- MVC、MVP與MVVM模式
- MVC、MVP與MVVM的區別
- MVVM的實現原理
- Object.defineProperty()方法
- ES6中定義的類和物件
- JS中的文件碎片
- 解構賦值
- Array.from
- Array.reduce
- 遞迴的使用
- Obj.keys()與Obj.defineProperty
- 釋出-訂閱模式
- vue專案優化,縮短首屏載入時間
深入拓展
- vue開發命令 npm run dev 輸入後的執行過程
- vue的伺服器端渲染
- 從零寫一個npm安裝包
- vue-cli中常用到的載入器
- webpack的特點