1. 程式人生 > >Vue原始碼分析之依賴收集(九)

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,基本思路一致,也是基於統計的思想,只不過其計算詞的權