Vue原始碼分析之依賴收集(九)
依賴收集就是訂閱資料變化watcher的收集,依賴收集的目的是當響應式資料發生變化時,能夠通知相應的訂閱者去處理相關的邏輯。
在上一章,介紹了Vue將普通物件變成響應式物件是利用defineReactive()(定義在'core/observer/index.js'中)函式,defineReactive()函式中主要關注的是新建一個dep = new Dep(),以及在設定屬性的getter時,對其做依賴收集:dep.depend()
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property&& property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = 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.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
定義在'core/obersver/dep.js'中的Dep類,主要功能是建立了observer與watcher之間的橋樑。depend()函式的作用是在呼叫Dep.target的addDep()函式。
這裡的Dep.target是一個靜態屬性,在全域性中唯一儲存著一個Watcher,因為在同一時間應該只有一個Watcher被計算。
export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
在watcher.js中定義了Watcher類,其中的deps和newDeps陣列中儲存了用來儲存watcher的依賴類。
其中的addDep()函式主要是先通過id判斷在新收集的依賴中是否有相同的依賴,沒有的話,將其儲存在newDeps陣列中,然後再判斷在以前儲存的依賴中是否有相同的,沒有則呼叫dep的addSub函式,將依賴的Watcher收集起來。這個過程主要是為了去重。
export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; computed: boolean; sync: boolean; dirty: boolean; active: boolean; dep: Dep; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.computed = !!options.computed this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.computed = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.computed // for computed watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter 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 ) } } if (this.computed) { this.value = undefined this.dep = new Dep() } else { this.value = this.get() } } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }
以渲染watcher為例說明一下過程。在執行$mount函式的過程中,最後會呼叫mountComponent函式,其中有一段這樣的程式碼。
在例項化Watcher是,將updateComponent函式傳入作為getter。
let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') }
在watcher中,會呼叫get函式
} else { this.value = this.get() }
可以看到,在get函式中,首先會pushTarget(),這個函式的功能是在將當前執行的Dep.target儲存起來。以便在元件巢狀時能夠恢復上一輪執行的Dep.target。
然後呼叫this.getter.call(vm,vm),這裡的getter就是updateComponent()函式,於是它會執行render函式。render函式在執行過程中會存在訪問資料的情況,於是就觸發getter。
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }
export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target }
在每個物件的屬性中都持有一個dep,在觸發getter之後,就會呼叫dep.depend( )函式,然後呼叫Dep.target.addDep( )函式,該函式中經過去重判斷之後,呼叫dep.addSub將渲染watcher儲存在subs陣列中。
回到watcher中的get( )函式,在執行完getter之後,還會執行traverse( )函式,這是為了將value中存在的屬性遞迴執行。
if (this.deep) { traverse(value) }
以及popTarget( )函式,該函式的作用是恢復上一輪執行的Dep.target。
popTarget()
然後執行cleanupDeps( ) 函式。其功能是判斷在新收集的依賴中是否出現在之前的依賴收集中,此前在addDep( ) 函式中也判斷過一次,但那個判斷的目的是為了在deps陣列和newDeps陣列中去重。這次判斷是為了丟掉不需要再監聽的屬性,避免重複觸發渲染。然後再將
this.cleanupDeps()
cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }
總結
在觸發資料的getter之後,會收集當前執行的watcher儲存在dep的subs陣列中。同時,在watcher類中,儲存了相關的deps和newDeps,用來到達去重的同時,還可以清除不再需要觀察的依賴。
相關推薦
Vue原始碼分析之依賴收集(九)
依賴收集就是訂閱資料變化watcher的收集,依賴收集的目的是當響應式資料發生變化時,能夠通知相應的訂閱者去處理相關的邏輯。在上一章,介紹了Vue將普通物件變成響應式物件是利用defineReactive()(定義在'core/observer/index.js'中)函式,d
elasticsearch原始碼分析之索引操作(九)
上節介紹了es的node啟動如何建立叢集服務的過程,這節在其基礎之上介紹es索引的基本操作功能(create、exist、delete),用來進一步細化es叢集是如果工作的。 客戶端部分的操作就不予介紹了,詳細可以參照elasticsearch原始碼分析之客戶
Spark原始碼分析之Spark Shell(上)
https://www.cnblogs.com/xing901022/p/6412619.html 文中分析的spark版本為apache的spark-2.1.0-bin-hadoop2.7。 bin目錄結構: -rwxr-xr-x. 1 bigdata bigdata 1089 Dec
雲客Drupal8原始碼分析之外掛系統(下)
以下內容僅是一個預覽,完整內容請見文尾: 至此本系列對外掛的介紹全部完成,涵蓋了系統外掛的所有知識 全文目錄(全文10476字): 例項化外掛 外掛對映Plugin mapping 外掛上下文
elasticsearch原始碼分析之分片分配(十)
分片 什麼是分片 分片是把索引資料切分成多個小的索引塊,這些小的索引塊能夠分發到同一個叢集中的不同節點。在檢索時,檢索結果是該索引每個分片上檢索結果的合併。類似於資料庫的分庫分表。 為什麼分片 1、這樣可以提高讀寫效能,實現負載均衡。 2、副本容易
elasticsearch原始碼分析之服務端(四)
上篇部落格說明了客戶端的情況,現在繼續分析服務端都幹了些啥,es是怎麼把資料插進去的,此處以transport的bulk為入口來探究,對於單個document的傳送就忽略了。 一、服務端接收 1.1接收訊息 在客戶端分析中已經提到,netty中通訊的處理類是Mes
elasticsearch原始碼分析之啟動過程(二)
最近開始廣泛的使用elasticsearch,也開始寫一些java程式碼了,為了提高java程式碼能力,也為了更加深入一點了解elasticsearch的內部運作機制,所以開始看一些elasticsearch的原始碼了。對於這種廣受追捧的開源專案,細細品讀一定會受益匪淺,
Android4.4.2原始碼分析之WiFi模組(一)
已經寫了幾篇關於Android原始碼的,原始碼程式碼量太大,所以如果想分析某個模組可能不知如何下手,說一下思路 1,分析原始碼英文閱讀能力要夠,想要分析某個模組一般找模組對應的英文,就是模組 2,找到之後首先檢視清單配置檔案Androidmani.fest,找到程式主介面activity 3,通過檢視配置檔
spark mllib原始碼分析之L-BFGS(一)
1. 使用 spark給出的example中涉及到LBFGS有兩個,分別是LBFGSExample.scala和LogisticRegressionWithLBFGSExample.scala,第一個是直接使用LBFGS直接訓練,需要指定一系列優化引數,優
雲客Drupal8原始碼分析之實體Entity(二)配置實體基類
配置實體基類是系統定義的一個用於配置實體的抽象基類,繼承自實體基類,完成了配置實體的大部分通用功能,具體的配置實體往往會繼承它,比如使用者角色實體,這樣寫少量程式碼即可,類定義如下: Drupal\Core\Config\Entity\ConfigEntityBase 實
雲客Drupal8原始碼分析之實體Entity(五)內容實體基類
原始碼分析重點在於在自己的大腦中重現開發者的思維過程,內容實體基類是drupal中很大的一個類,她要處理眾多的問題,內容實體的大多數功能都集中在這裡,開發者有許多的考慮,要弄清楚她的所有細節,學習者可能會覺得有些困難,這時需要明白任何複雜龐大的事物都是一步步累積發展起來的,
雲客Drupal8原始碼分析之外掛系統(上)
各位《雲客drupal8原始碼分析》系列的讀者: 本系列一直以每週一篇的速度進行部落格原創更新,希望幫助大家理解drupal8底層原理,並縮短學習時間,但自《外掛系統(上)》主題開始部落格僅釋出前言和目錄,這是因為雲客在思考一個問題:drupal在國外如此流行但在國內卻很小
Android4.4.2原始碼分析之WiFi模組(二)
接著上一篇繼續對WiFi原始碼的分析 onResume方法中 6>,首先是呼叫WiFiEnabler的resume方法對switch進行管理 接下來註冊廣播 getActivity().registerReceiver(mReceiver, mFilter);
Memcached原始碼分析之訊息迴應(3)
文章列表: 《Memcached原始碼分析 - Memcached原始碼分析之總結篇(8)》 前言 上一章《Memcached原始碼分析 - Memcached原始碼分析之命令解析(2)》,我們花了很大的力氣去講解Memcached如何從客戶端讀取命令,並且
Vue學習之原始碼分析--Vue.js依賴收集(二)
為什麼要依賴收集 先看下面這段程式碼 new Vue({ template: `<div> <span>text1:</span> {{text1}}
用Vue來實現音樂播放器(九):歌單數據接口分析
QQ 插件 但是 之間 nbsp 跨域問題 前端 代理服務 一點 z這裏如果我們和之前獲取輪播圖的數據一樣來獲取表單的數據 發現根本獲取不到 原因是qq音樂在請求頭裏面加了authority和refer等 但是如果我們通過jsonp實現跨域
Vue 原始碼分析之proxy代理
Vue 原始碼分析之proxy代理 當我們在使用Vue進行資料設定時,通常初始化格式為: let data = { age: 12, name: 'yang' } // 例項化Vue物件 let vm = new Vue({ data })
Vue實戰指南之依賴注入(provide / inject)
案例 UI美眉說咱家的選項選單太醜了,小哥哥能不能美化一下呀,灑家自然是說小意思啦~ 自定義一個select元件,so easy~ 簡單粗暴型: <el-select v-model="favourite" :option="[]"></el-select> option作為資
Uboot啟動過程原始碼分析之第一階段(硬體相關)
從上一個部落格知道uboot的入口點在 cpu/arm920t/start.s 開啟cpu/arm920t/start.s 跳轉到reset reset: /* * set the cpu to SVC32 mode// CUP設定為管理模式 */ mrs r0,cps
【NLP】【三】jieba原始碼分析之關鍵字提取(TF-IDF/TextRank)
【一】綜述 利用jieba進行關鍵字提取時,有兩種介面。一個基於TF-IDF演算法,一個基於TextRank演算法。TF-IDF演算法,完全基於詞頻統計來計算詞的權重,然後排序,在返回TopK個詞作為關鍵字。TextRank相對於TF-IDF,基本思路一致,也是基於統計的思想,只不過其計算詞的權