Vue的變化偵測原理
什麼是變化偵測
Vue.js會自動通過狀態生成DOM,並將其輸出到頁面上顯示出來,這個過程叫渲染。Vue.js的渲染過程是宣告式的,我們通過模板來描述狀態與DOM之間的對映關係。
通常,在執行時應用內部的狀態會不斷髮生變化,此時需要不停地重新渲染。這時如何確定狀態中發生了什麼變化?
變化偵測就是用來解決這個問題的,它分為兩種型別:一種是“推”(push),另一種是“拉”(pull)。
Angular和React中的變化偵測都屬於“拉”,這就是說當狀態發生變化時,它不知道哪個狀態變了,只知道狀態有可能變了,然後會傳送一個訊號告訴框架,框架內部收到訊號後,會進行一個暴力比對來找出哪些DOM節點需要重新渲染。這在Angular中是髒檢查的流程,在React中使用的是虛擬DOM。
而Vue.js的變化偵測屬於“推”。當狀態發生變化時,Vue.js立刻就知道了,而且在一定程度上知道哪些狀態變了。因此,它知道的資訊更多,也就可以進行更細粒度的更新。
所謂更細粒度的更新,就是說:假如有一個狀態繫結著好多個依賴,每個依賴表示一個具體的DOM節點,那麼當這個狀態發生變化時,向這個狀態的所有依賴傳送通知,讓它們進行DOM更新操作。相比較而言,“拉”的粒度是最粗的。
但是它也有一定的代價,因為粒度越細,每個狀態所繫結的依賴就越多,依賴追蹤在記憶體上的開銷就會越大。因此,從Vue.js 2.0開始,它引入了虛擬DOM,將粒度調整為中等粒度,即一個狀態所繫結的依賴不再是具體的DOM節點,而是一個元件。這樣狀態變化後,會通知到元件,元件內部再使用虛擬DOM進行比對。這可以大大降低依賴數量,從而降低依賴追蹤所消耗的記憶體。
Vue.js之所以能隨意調整粒度,本質上還要歸功於變化偵測。因為“推”型別的變化偵測可以隨意調整粒度。
如何追蹤變化
Object.defineProperty和ES6中的Proxy
Observer
Observer類會附加到每一個被偵測的object上。一旦被附加上,Observer會將object的所有屬性轉換為getter/setter的形式。來收集屬性的依賴,並且當屬性發生變化時會通知這些依賴
import Dep from './Dep';
export class Observer {
constructor(value) {
this.value = value;
if (!Array.isArray(value)) {
this.walk(value);
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
function defineReactive(data, key, val) {
if (typeof val === 'object') {
new Observer(val);
}
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
dep.depend();//收集依賴
return val
},
set(newVal) {
if (val === newVal) {
return
}
val = newVal;
dep.notify();//觸發依賴
}
})
}
Dep
它用來收集依賴、刪除依賴和向依賴傳送訊息等。
import { Watcher } from "./Watcher";
export class Dep {
target; //target: ?Watcher;
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.subs, sub);
}
depend(){
if(this.target instanceof Watcher){
this.addSub(this.target);
}
}
notify(){
const subs=this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
function remove(arr, item) {
if (arr.length) {
const index = arr.findIndex(item);
if (index > -1) {
this.subs.splice(index, 1);
}
}
}
Watcher
Watcher
是一箇中介的角色,資料發生變化時通知它,然後它再通知其他地方。
import { Dep } from "./Dep";
export class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;// vm指當前的Vue例項
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();// 讀取vm.$data中的值,同時會觸發屬性上的getter
}
get() {
// Watcher把自己設定到全域性唯一的指定位置,在這裡就是Dep.target
Dep.target = this;
//讀取資料,觸發這個資料的getter。因此Observer會收集依賴,將這個Watcher收集到Dep,也就是依賴收集。
let value = this.getter.call(this.vm, this.vm);
//收集結束,清除Dep.target的內容
Dep.target = null;
//返回讀取到的資料值
return value
}
update() {
//資料改變之後,Dep會依次迴圈向依賴發通知,這裡接到通知之後,先獲取之前的舊資料
const oldValue = this.value;
//然後獲取最新的值
this.value = this.get();
//將新舊值傳給回撥函式
this.cb.call(this.vm, this.value, oldValue);
}
}
const bailRE = /[^\w.$]/
export function parsePath(path) {
if (bailRE.tetx(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) { return; }
obj = obj[segments[i]]
}
return obj;
}
}
總結綜述
變化偵測就是偵測資料的變化。當資料發生變化時,要能偵測到併發出通知。
Object
可以通過Object.defineProperty
將屬性轉換成getter/setter的形式來追蹤變化。讀取資料時會觸發getter,修改資料時會觸發setter。
我們需要在getter中收集有哪些依賴使用了資料。當setter被觸發時,去通知getter中收集的依賴資料發生了變化。
收集依賴需要為依賴找一個儲存依賴的地方,為此我們建立了Dep
,它用來收集依賴、刪除依賴和向依賴傳送訊息等。
所謂的依賴,其實就是Watcher
。只有Watcher
觸發的getter才會收集依賴,哪個Watcher
觸發了getter,就把哪個Watcher
收集到Dep
中。當資料發生變化時,會迴圈依賴列表,把所有的Watcher
都通知一遍。
Watcher
的原理是先把自己設定到全域性唯一的指定位置(例如window.target
),然後讀取資料。因為讀取了資料,所以會觸發這個資料的getter。接著,在getter中就會從全域性唯一的那個位置讀取當前正在讀取資料的Watcher
,並把這個Watcher
收集到Dep
中去。通過這樣的方式,Watcher
可以主動去訂閱任意一個資料的變化。
此外,我們建立了Observer
類,它的作用是把一個object
中的所有資料(包括子資料)都轉換成響應式的,也就是它會偵測object
中所有資料(包括子資料)的變化。
由於在ES6之前JavaScript並沒有提供超程式設計的能力,所以在物件上新增屬性和刪除屬性都無法被追蹤到。
Data、Observer、Dep和Watcher之間的關係
Data
通過Observer
轉換成了getter/setter的形式來追蹤變化。
當外界通過Watcher
讀取資料時,會觸發getter從而將Watcher
新增到依賴中。
當資料發生了變化時,會觸發setter,從而向Dep
中的依賴(Watcher
)傳送通知。
Watcher
接收到通知後,會向外界傳送通知,變化通知到外界後可能會觸發檢視更新,也有可能觸發使用者的某個回撥函式等。
參考資料:
相關文章
- 深入淺出 - vue變化偵測原理Vue
- Vue陣列變化的偵測的學習Vue陣列
- 聊一聊cc的變化偵測和hook實現Hook
- Vue響應式原理-如何監聽Array的變化?Vue
- 【原始碼系列#04】Vue3偵聽器原理(Watch)原始碼Vue
- 015.Vue3入門,偵聽器的使用,響應式資料變化就自動執行Vue
- JavaScript 工作原理之十-使用 MutationObserver 監測 DOM 變化JavaScriptServer
- vue 監聽路由變化Vue路由
- Vue EventBus事件偵聽($on、$emit、$off、$once)Vue事件MIT
- React和Vue中,是如何監聽變數變化的ReactVue變數
- 硬核技術宅偵探和他的007黑貓——《迷霧偵探》評測
- vue是如何監聽陣列變化的Vue陣列
- 用這招監聽 Vue 的插槽變化Vue
- Vue計算屬性和偵聽器Vue
- 複習Vue中的方法,計算和偵聽者Vue
- Vue.js中偵聽器watch 的高階用法Vue.js
- vue2-使用watch監聽路由的變化Vue路由
- Vue中的計算屬性和偵聽器比較Vue
- Vue中計算屬性和偵聽器Vue
- 前端【VUE】03-vue【computed 計算屬性】【watch 偵聽器】前端Vue
- 【譯】淺談Angular中的變化檢測Angular
- webpack(8)vue元件化開發的演變過程WebVue元件化
- 極速上手 VUE 3—v-model 的使用變化Vue
- vue中如何監聽vuex中的資料變化Vue
- JavaScript 偵測手機瀏覽器的五種方法JavaScript瀏覽器
- Vue原始碼學習: 關於對Array的資料偵聽Vue原始碼
- 精準化測試原理簡介
- vue(js) 拖拽改變排序(陣列)位置(原理及程式碼)VueJS排序陣列
- 在VUE中改變陣列、物件。頁面沒有變化Vue陣列物件
- Vue中計算屬性computed與偵聽器watch的區別Vue
- vue中陣列變動不被監測問題Vue陣列
- 如何準確有效偵測、分析網路流量
- Vue中的底層原理Vue
- vue原理初探Vue
- 為什麼Vue不能觀察到陣列length的變化?Vue陣列
- Vue3.x 關於元件的那些變化(新手必看篇)Vue元件
- 深入理解Vue 3:計算屬性與偵聽器的藝術Vue
- 測量、基線和效能優化之三:基於測量、基線和變化的效能優化優化