Vue 源碼分析之 Observer實(shí)現(xiàn)過程
導(dǎo)語:
本文是對(duì) Vue 官方文檔深入響應(yīng)式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通過源碼還原實(shí)現(xiàn)過程。
響應(yīng)式原理可分為兩步,依賴收集的過程與觸發(fā)-重新渲染的過程。依賴收集的過程,有三個(gè)很重要的類,分別是 Watcher、Dep、Observer。本文主要解讀 Observer 。
這篇文章講解上篇文章沒有覆蓋到的 Observer 部分的內(nèi)容,還是先看官網(wǎng)這張圖:
Observer 最主要的作用就是實(shí)現(xiàn)了上圖中touch -Data(getter) - Collect as Dependency這段過程,也就是依賴收集的過程。
還是以下面的代碼為例子進(jìn)行梳理:
(注:左右滑動(dòng)即可查看完整代碼,下同)
varvm = newVue({ el: '#demo', data: { firstName: 'Hello', fullName: '' }, watch: { firstName(val) { this.fullName = val + 'TalkingData'; }, } })
在源碼中,通過還原Vue 進(jìn)行實(shí)例化的過程,從開始一步一步到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 方法中,開始了對(duì)data 項(xiàng)中的數(shù)據(jù)進(jìn)行“觀察”,會(huì)將所有數(shù)據(jù)的變成observable 的。接下來看observe 方法的代碼:
// src/core/observer/index.js functionobserve(value: any, asRootData: ?boolean): Observer| void{ // 如果不是對(duì)象,直接返回 if(!isObject(value) || value instanceofVNode) { return } letob: Observer | void if(hasOwn(value, '__ob__') && value.__ob__ instanceofObserver) { // 如果有實(shí)例則返回實(shí)例 ob = value.__ob__ } elseif( // 確保value是單純的對(duì)象,而不是函數(shù)或者是Regexp等情況 observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 實(shí)例化一個(gè) Observer ob = newObserver(value) } if(asRootData && ob) { ob.vmCount++ } returnob }
observe 方法的作用是給data 創(chuàng)建一個(gè)Observer 實(shí)例并返回,如果data 有ob屬性了,說明已經(jīng)有Observer 實(shí)例了,則返回現(xiàn)有的實(shí)例。Vue 的響應(yīng)式數(shù)據(jù)都會(huì)有一個(gè)ob的屬性,里面存放了該屬性的Observer 實(shí)例,防止重復(fù)綁定。再來看new Observer(value) 過程中發(fā)生了什么:
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 = 0 def(value, '__ob__', this) if(Array.isArray(value)) { // ... this.observeArray(value) } else{ this.walk(value) } } walk (obj: Object) { constkeys = Object.keys(obj) for(leti = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } observeArray (items: Array<any>) { for(leti = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
通過源碼可以看到,實(shí)例化Observer 過程中主要是做了兩個(gè)判斷。如果是數(shù)組,則對(duì)數(shù)組里面的每一項(xiàng)再次調(diào)用oberser 方法進(jìn)行觀察;如果是非數(shù)組的對(duì)象,遍歷對(duì)象的每一個(gè)屬性,對(duì)其調(diào)用defineReactive 方法。這里的defineReactive 方法就是核心!通過使用Object.defineProperty 方法對(duì)每一個(gè)需要被觀察的屬性添加get/set,完成依賴收集。依賴收集過后,每個(gè)屬性都會(huì)有一個(gè)Dep 來保存所有Watcher 對(duì)象。按照文章最開始的例子來講,就是對(duì)firstName和fullName分別添加了get/set,并且它們各自有一個(gè)Dep 實(shí)例來保存各自觀察它們的所有Watcher 對(duì)象。下面是defineReactive 的源碼:
exportfunctiondefineReactive( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { constdep = newDep() // 獲取屬性的自身描述符 constproperty = Object.getOwnPropertyDeor(obj, key) if(property && property.configurable === false) { return } // cater for pre-defined getter/setters // 檢查屬性之前是否設(shè)置了 getter/setter // 如果設(shè)置了,則在之后的 get/set 方法中執(zhí)行設(shè)置了的 getter/setter constgetter = property && property.get constsetter = property && property.set // 通過對(duì)屬性再次調(diào)用 observe 方法來判斷是否有子對(duì)象 // 如果有子對(duì)象,對(duì)子對(duì)象也進(jìn)行依賴搜集 letchildOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: functionreactiveGetter() { // 如果屬性原本擁有g(shù)etter方法則執(zhí)行 constvalue = getter ? getter.call(obj) : val if(Dep.target) { // 進(jìn)行依賴收集 dep.depend() if(childOb) { // 如果有子對(duì)象,對(duì)子對(duì)象也進(jìn)行依賴搜集 childOb.dep.depend() // 如果屬性是數(shù)組,則對(duì)每一個(gè)項(xiàng)都進(jìn)行依賴收集 // 如果某一項(xiàng)還是數(shù)組,則遞歸 if(Array.isArray(value)) { dependArray(value) } } } returnvalue }, set: functionreactiveSetter(newVal) { // 如果屬性原本擁有g(shù)etter方法則執(zhí)行 // 通過getter方法獲取當(dāng)前值,與新值進(jìn)行比較 // 如果新舊值一樣則不需要執(zhí)行下面的操作 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方法則執(zhí)行 setter.call(obj, newVal) } else{ // 如果原本沒有setter則直接賦新值 val = newVal } // 判斷新的值是否有子對(duì)象,有的話繼續(xù)觀察子對(duì)象 childOb = !shallow && observe(newVal) // 通知所有的觀察者,更新狀態(tài) dep.notify() } }) }
按照源碼中的中文注釋,應(yīng)該可以明白defineReactive 執(zhí)行的過程中做了哪些工作。其實(shí)整個(gè)過程就是遞歸,為每個(gè)屬性添加getter/setter。對(duì)于getter/setter,同樣也需要對(duì)每一個(gè)屬性進(jìn)行遞歸(判斷子對(duì)象)的完成觀察者模式。對(duì)于getter,用來完成依賴收集,即源碼中的dep.depend()。對(duì)于setter,一旦一個(gè)數(shù)據(jù)觸發(fā)其set方法,便會(huì)發(fā)布更新消息,通知這個(gè)數(shù)據(jù)的所有觀察者也要發(fā)生改變。即源碼中的dep.notify()。
總結(jié)
以上所述是小編給大家介紹的 Vue 源碼分析之 Observer實(shí)現(xiàn)過程,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Vue之Dep和Observer的用法及說明
- vue3?圖片懶加載的兩種方式、IntersectionObserver和useIntersectionObserver實(shí)例詳解
- 關(guān)于Vue?"__ob__:Observer"屬性的解決方案詳析
- Vue2?Observer實(shí)例dep和閉包中dep區(qū)別詳解
- vue中關(guān)于_ob_:observer的處理方式
- Vue數(shù)組中出現(xiàn)__ob__:Observer無法取值問題的解決方法
- Vue響應(yīng)式原理Observer、Dep、Watcher理解
- vue中__ob__:?Observer的踩坑記錄
相關(guān)文章
vue實(shí)現(xiàn)網(wǎng)易云音樂純界面
這篇文章主要為大家介紹了vue實(shí)現(xiàn)網(wǎng)易云音樂純界面過程詳解,附含詳細(xì)源碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07vue中swiper?vue-awesome-swiper的使用方法及各種坑解決
這篇文章主要介紹了vue中swiper?vue-awesome-swiper的使用方法及各種坑解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01element table列表根據(jù)數(shù)據(jù)設(shè)置背景色
在使用elementui中的el-table時(shí),需要將表的背景色和字體顏色設(shè)置為新顏色,本文就來介紹一下element table列表根據(jù)數(shù)據(jù)設(shè)置背景色,感興趣的可以了解一下2023-08-08JavaScript之實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue示例
這篇文章主要介紹了JavaScript之實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01Vue 權(quán)限控制的兩種方法(路由驗(yàn)證)
這篇文章主要介紹了Vue 權(quán)限控制的兩種方法(路由驗(yàn)證),每種方法給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08