當前位置:
首頁 > 最新 > Vue 源碼分析之 Observer

Vue 源碼分析之 Observer

作者:TalkingData 張成斌

本文由TalkingData原創,轉載請獲取授權。

導語:

本文是對 Vue 官方文檔深入響應式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,並通過源碼還原實現過程。

響應式原理可分為兩步,依賴收集的過程與觸發-重新渲染的過程。依賴收集的過程,有三個很重要的類,分別是 Watcher、Dep、Observer。本文主要解讀 Observer ,對Watcher和Dep的解讀請查看第一篇文章:技術專欄 | Vue源碼分析之 Watcher 和 Dep

這篇文章講解上篇文章沒有覆蓋到的 Observer 部分的內容,還是先看官網這張圖:

Observer最主要的作用就是實現了上圖中touch -Data(getter) - Collect as Dependency這段過程,也就是依賴收集的過程。

還是以下面的代碼為例子進行梳理:

(註:左右滑動即可查看完整代碼,下同)

varvm =newVue({

el:"#demo",

data: {

firstName:"Hello",

fullName:""

},

watch: {

firstName(val) {

this.fullName = val +"TalkingData";

},

}

})

在源碼中,通過還原Vue進行實例化的過程,從開始一步一步到Observer類的源碼依次為(省略了很多不在本篇文章討論的代碼):

// src/core/instance/index.js

functionVue(options){

if(process.env.NODE_ENV !=="production"&&

!(thisinstanceofVue)

) {

warn("Vue is a constructor and should be called with the `new` keyword")

}

this._init(options)

}

// src/core/instance/init.js

Vue.prototype._init =function(options?: Object){

constvm: Component =this

// ...

initState(vm)

// ...

}

// src/core/instance/state.js

exportfunctioninitState(vm: Component){

// ...

constopts = vm.$options

if(opts.data) {

initData(vm)

}

// ...

}

functioninitData(vm: Component){

letdata = vm.$options.data

data = vm._data =typeofdata ==="function"

? getData(data, vm)

: data || {}

// ...

// observe data

observe(data,true/* asRootData */)

}

在initData方法中,開始了對data項中的數據進行「觀察」,會將所有數據的變成observable的。接下來看observe方法的代碼:

// src/core/observer/index.js

functionobserve(value: any, asRootData: ?boolean):Observer|void{

// 如果不是對象,直接返回

if(!isObject(value) || valueinstanceofVNode) {

return

}

letob: Observer |void

if(hasOwn(value,"__ob__") && value.__ob__instanceofObserver) {

// 如果有實例則返回實例

ob = value.__ob__

}elseif(

// 確保value是單純的對象,而不是函數或者是Regexp等情況

observerState.shouldConvert &&

!isServerRendering() &&

(Array.isArray(value) || isPlainObject(value)) &&

Object.isExtensible(value) &&

!value._isVue

) {

// 實例化一個 Observer

ob =newObserver(value)

}

if(asRootData && ob) {

ob.vmCount++

}

returnob

}

observe方法的作用是給data創建一個Observer實例並返回,如果data有ob屬性了,說明已經有Observer實例了,則返回現有的實例。Vue的響應式數據都會有一個ob的屬性,裡面存放了該屬性的Observer實例,防止重複綁定。再來看new Observer(value)過程中發生了什麼:

exportclassObserver{

value: any;

dep: Dep;

vmCount: number;// number of vms that has this object as root $data

constructor(value: any) {

this.value = value

this.dep =newDep()

this.vmCount =

def(value,"__ob__",this)

if(Array.isArray(value)) {

// ...

this.observeArray(value)

}else{

this.walk(value)

}

}

walk (obj:Object) {

constkeys =Object.keys(obj)

for(leti =; i

defineReactive(obj, keys[i], obj[keys[i]])

}

}

observeArray (items:Array) {

for(leti =, l = items.length; i

observe(items[i])

}

}

}

通過源碼可以看到,實例化Observer過程中主要是做了兩個判斷。如果是數組,則對數組裡面的每一項再次調用oberser方法進行觀察;如果是非數組的對象,遍歷對象的每一個屬性,對其調用defineReactive方法。這裡的defineReactive方法就是核心!通過使用Object.defineProperty方法對每一個需要被觀察的屬性添加get/set,完成依賴收集。依賴收集過後,每個屬性都會有一個Dep來保存所有Watcher對象。按照文章最開始的例子來講,就是對firstName和fullName分別添加了get/set,並且它們各自有一個Dep實例來保存各自觀察它們的所有Watcher對象。下面是defineReactive的源碼:

exportfunctiondefineReactive(

obj: Object,

key: string,

val: any,

customSetter?: ?Function,

shallow?: boolean

){

constdep =newDep()

// 獲取屬性的自身描述符

constproperty =Object.getOwnPropertyDescriptor(obj, key)

if(property && property.configurable ===false) {

return

}

// cater for pre-defined getter/setters

// 檢查屬性之前是否設置了 getter/setter

// 如果設置了,則在之後的 get/set 方法中執行設置了的 getter/setter

constgetter = property && property.get

constsetter = property && property.set

// 通過對屬性再次調用 observe 方法來判斷是否有子對象

// 如果有子對象,對子對象也進行依賴搜集

letchildOb = !shallow && observe(val)

Object.defineProperty(obj, key, {

enumerable:true,

configurable:true,

get:functionreactiveGetter(){

// 如果屬性原本擁有getter方法則執行

constvalue = getter ? getter.call(obj) : val

if(Dep.target) {

// 進行依賴收集

dep.depend()

if(childOb) {

// 如果有子對象,對子對象也進行依賴搜集

childOb.dep.depend()

// 如果屬性是數組,則對每一個項都進行依賴收集

// 如果某一項還是數組,則遞歸

if(Array.isArray(value)) {

dependArray(value)

}

}

}

returnvalue

},

set:functionreactiveSetter(newVal){

// 如果屬性原本擁有getter方法則執行

// 通過getter方法獲取當前值,與新值進行比較

// 如果新舊值一樣則不需要執行下面的操作

constvalue = getter ? getter.call(obj) : val

/* eslint-disable no-self-compare */

if(newVal === value || (newVal !== newVal && value !== value)) {

return

}

/* eslint-enable no-self-compare */

if(process.env.NODE_ENV !=="production"&& customSetter) {

customSetter()

}

if(setter) {

// 如果屬性原本擁有setter方法則執行

setter.call(obj, newVal)

}else{

// 如果原本沒有setter則直接賦新值

val = newVal

}

// 判斷新的值是否有子對象,有的話繼續觀察子對象

childOb = !shallow && observe(newVal)

// 通知所有的觀察者,更新狀態

dep.notify()

}

})

}

按照源碼中的中文注釋,應該可以明白defineReactive執行的過程中做了哪些工作。其實整個過程就是遞歸,為每個屬性添加getter/setter。對於getter/setter,同樣也需要對每一個屬性進行遞歸(判斷子對象)的完成觀察者模式。對於getter,用來完成依賴收集,即源碼中的dep.depend()。對於setter,一旦一個數據觸發其set方法,便會發布更新消息,通知這個數據的所有觀察者也要發生改變。即源碼中的dep.notify()。

- End -


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 TalkingData 的精彩文章:

2017年最受歡迎文章TOP10

TAG:TalkingData |