Vue.js原理分析之observer模塊詳解
介紹
observer是Vue核心中最重要的一個(gè)模塊(個(gè)人認(rèn)為),能夠?qū)崿F(xiàn)視圖與數(shù)據(jù)的響應(yīng)式更新,底層全憑observer的支持。
注意:本文是針對(duì)Vue@2.1.8進(jìn)行分析
observer模塊在Vue項(xiàng)目中的代碼位置是src/core/observer,模塊共分為這幾個(gè)部分:
- Observer: 數(shù)據(jù)的觀察者,讓數(shù)據(jù)對(duì)象的讀寫操作都處于自己的監(jiān)管之下
- Watcher: 數(shù)據(jù)的訂閱者,數(shù)據(jù)的變化會(huì)通知到Watcher,然后由Watcher進(jìn)行相應(yīng)的操作,例如更新視圖
- Dep: Observer與Watcher的紐帶,當(dāng)數(shù)據(jù)變化時(shí),會(huì)被Observer觀察到,然后由Dep通知到Watcher
示意圖如下:

Observer
Observer類定義在src/core/observer/index.js中,先來看一下Observer的構(gòu)造函數(shù)
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
value是需要被觀察的數(shù)據(jù)對(duì)象,在構(gòu)造函數(shù)中,會(huì)給value增加__ob__屬性,作為數(shù)據(jù)已經(jīng)被Observer觀察的標(biāo)志。如果value是數(shù)組,就使用observeArray遍歷value,對(duì)value中每一個(gè)元素調(diào)用observe分別進(jìn)行觀察。如果value是對(duì)象,則使用walk遍歷value上每個(gè)key,對(duì)每個(gè)key調(diào)用defineReactive來獲得該key的set/get控制權(quán)。
解釋下上面用到的幾個(gè)函數(shù)的功能:
- observeArray: 遍歷數(shù)組,對(duì)數(shù)組的每個(gè)元素調(diào)用observe
- observe: 檢查對(duì)象上是否有__ob__屬性,如果存在,則表明該對(duì)象已經(jīng)處于Observer的觀察中,如果不存在,則new Observer來觀察對(duì)象(其實(shí)還有一些判斷邏輯,為了便于理解就不贅述了)
- walk: 遍歷對(duì)象的每個(gè)key,對(duì)對(duì)象上每個(gè)key的數(shù)據(jù)調(diào)用defineReactive
- defineReactive: 通過Object.defineProperty設(shè)置對(duì)象的key屬性,使得能夠捕獲到該屬性值的set/get動(dòng)作。一般是由Watcher的實(shí)例對(duì)象進(jìn)行g(shù)et操作,此時(shí)Watcher的實(shí)例對(duì)象將被自動(dòng)添加到Dep實(shí)例的依賴數(shù)組中,在外部操作觸發(fā)了set時(shí),將通過Dep實(shí)例的notify來通知所有依賴的watcher進(jìn)行更新。
如果不太理解上面的文字描述可以看一下圖:

Dep
Dep是Observer與Watcher之間的紐帶,也可以認(rèn)為Dep是服務(wù)于Observer的訂閱系統(tǒng)。Watcher訂閱某個(gè)Observer的Dep,當(dāng)Observer觀察的數(shù)據(jù)發(fā)生變化時(shí),通過Dep通知各個(gè)已經(jīng)訂閱的Watcher。
Dep提供了幾個(gè)接口:
- addSub: 接收的參數(shù)為Watcher實(shí)例,并把Watcher實(shí)例存入記錄依賴的數(shù)組中
- removeSub: 與addSub對(duì)應(yīng),作用是將Watcher實(shí)例從記錄依賴的數(shù)組中移除
- depend: Dep.target上存放這當(dāng)前需要操作的Watcher實(shí)例,調(diào)用depend會(huì)調(diào)用該Watcher實(shí)例的addDep方法,addDep的功能可以看下面對(duì)Watcher的介紹
- notify: 通知依賴數(shù)組中所有的watcher進(jìn)行更新操作
Watcher
Watcher是用來訂閱數(shù)據(jù)的變化的并執(zhí)行相應(yīng)操作(例如更新視圖)的。Watcher的構(gòu)造器函數(shù)定義如下:
constructor (vm, expOrFn, cb, options) {
this.vm = vm
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
參數(shù)中,vm表示組件實(shí)例,expOrFn表示要訂閱的數(shù)據(jù)字段(字符串表示,例如a.b.c)或是一個(gè)要執(zhí)行的函數(shù),cb表示watcher運(yùn)行后的回調(diào)函數(shù),options是選項(xiàng)對(duì)象,包含deep、user、lazy等配置。
watcher實(shí)例上有這些方法:
- get: 將Dep.target設(shè)置為當(dāng)前watcher實(shí)例,在內(nèi)部調(diào)用this.getter,如果此時(shí)某個(gè)被Observer觀察的數(shù)據(jù)對(duì)象被取值了,那么當(dāng)前watcher實(shí)例將會(huì)自動(dòng)訂閱數(shù)據(jù)對(duì)象的Dep實(shí)例
- addDep: 接收參數(shù)dep(Dep實(shí)例),讓當(dāng)前watcher訂閱dep
- cleanupDeps: 清除newDepIds和newDep上記錄的對(duì)dep的訂閱信息
- update: 立刻運(yùn)行watcher或者將watcher加入隊(duì)列中等待統(tǒng)一flush
- run: 運(yùn)行watcher,調(diào)用this.get()求值,然后觸發(fā)回調(diào)
- evaluate: 調(diào)用this.get()求值
- depend: 遍歷this.deps,讓當(dāng)前watcher實(shí)例訂閱所有dep
- teardown: 去除當(dāng)前watcher實(shí)例所有的訂閱
Array methods
在src/core/observer/array.js中,Vue框架對(duì)數(shù)組的push、pop、shift、unshift、sort、splice、reverse方法進(jìn)行了改造,在調(diào)用數(shù)組的這些方法時(shí),自動(dòng)觸發(fā)dep.notify(),解決了調(diào)用這些函數(shù)改變數(shù)組后無法觸發(fā)更新的問題。
在Vue的官方文檔中對(duì)這個(gè)也有說明:http://cn.vuejs.org/v2/guide/list.html#變異方法
總結(jié)
以上就是這篇文中的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
相關(guān)文章
vue3的defineExpose宏函數(shù)是如何暴露方法給父組件使用
當(dāng)子組件使用setup后,父組件就不能像vue2那樣直接就可以訪問子組件內(nèi)的屬性和方法,這個(gè)時(shí)候就需要在子組件內(nèi)使用defineExpose宏函數(shù)來指定想要暴露出去的屬性和方法,本文介紹vue3的defineExpose宏函數(shù)是如何暴露方法給父組件使用,需要的朋友可以參考下2024-05-05
Vue項(xiàng)目中ESLint配置超全指南(VScode)
ESLint是一個(gè)代碼檢查工具,用來檢查你的代碼是否符合指定的規(guī)范,下面這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目中ESLint配置(VScode)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
vue-element內(nèi)table插入超鏈接a標(biāo)簽的使用
在Vue Element的table組件中插入超鏈接,可以使用<el-link>標(biāo)簽替代傳統(tǒng)的<a>標(biāo)簽,實(shí)現(xiàn)更加整潔的UI設(shè)計(jì),具體操作是替換原有的<span>標(biāo)簽,直接使用<el-link>進(jìn)行超鏈接的插入,使得鏈接樣式與Element UI保持一致2024-09-09
解決父組件將子組件作為彈窗調(diào)用只執(zhí)行一次created的問題
這篇文章主要介紹了解決父組件將子組件作為彈窗調(diào)用只執(zhí)行一次created的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Vue3使用TypeIt實(shí)現(xiàn)文字打字機(jī)效果的代碼示例
在現(xiàn)代網(wǎng)頁設(shè)計(jì)中,文字打字機(jī)效果是一種非常流行的動(dòng)畫效果,能夠吸引用戶的注意力并提升用戶體驗(yàn),本文將介紹如何在 Vue 3 中使用 TypeIt 庫實(shí)現(xiàn)文字打字機(jī)效果,并分享一些實(shí)用的技巧和示例,需要的朋友可以參考下2025-01-01
vue項(xiàng)目使用websocket連接問題及解決
這篇文章主要介紹了vue項(xiàng)目使用websocket連接問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Vue中this.$refs獲取為undefined的原因和解決辦法(this.$refs.屬性為undefined原因
在Vue項(xiàng)目開發(fā)中,使用this.$refs訪問組件或DOM元素的引用時(shí),可能會(huì)遇到獲取為undefined的情況,這篇文章主要介紹了Vue中this.$refs獲取為undefined的原因和解決辦法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-11-11

