Vue.js源碼閱讀六水、八

在Vue實例初始化的過程中烟阐,initState方法會調(diào)用initComputedinitWatch來分別初始化計算屬性和偵聽屬性,那么接下來就分析這兩個方法的實現(xiàn)拧晕。

計算屬性

這兩個方法都定義在core/instance/state.js

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

核心是首先是創(chuàng)建了vm._computedWatchers屬性并設(shè)為一個空對象隙姿,然后對每個定義的計算屬性調(diào)用創(chuàng)建一個Watcher并調(diào)用defineComputed方法:

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

計算屬性的定義可以是一個函數(shù)或者一個對象。如果是一個函數(shù)厂捞,那么它將作為這個計算屬性的getter输玷。如果是一個對象,那么那么這個對象的get屬性和set屬性分別是這個計算屬性的getter和setter靡馁,同時可以設(shè)置cache=false來禁止緩存欲鹏。
若果是需要緩存的情況,getter將被設(shè)為createComputedGetter方法的返回值臭墨。

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      watcher.depend()
      return watcher.evaluate()
    }
  }
}

計算屬性創(chuàng)建的Watcher與普通的Watcher的不同之處是赔嚎,在Watcher的構(gòu)造函數(shù)中有這么一段邏輯:

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    // ...
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

在組件掛載時創(chuàng)建的Watcher尤误,this.valueupdateComponent函數(shù),也就是創(chuàng)建Watcher的時候會立即做一次組件更新结缚。而計算屬性創(chuàng)建的Watcher沒有立即調(diào)用this.value,而是創(chuàng)建了一個Dep實例红竭。

當(dāng)組件渲染時訪問到計算屬性,就會調(diào)用它的getter茵宪。首先會拿到它對應(yīng)的watcher,執(zhí)行watcher.depend():

  depend () {
    if (this.dep && Dep.target) {
      this.dep.depend()
    }
  }

此時Dep.target是當(dāng)前正在渲染的組件的Watcher眉厨,也就是讓當(dāng)前的活動正在渲染的組件訂閱了這個計算屬性的變化。

然后調(diào)用了watcher.evaluate:

  evaluate () {
    if (this.dirty) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

如果this.dirtytrue那么就執(zhí)行this.get()憾股,返回計算屬性的值。

this.get()會調(diào)用pushTarget(this)Dep.target設(shè)為自身服球,那么如果在執(zhí)行g(shù)etter的過程中依賴了其他的響應(yīng)式屬性茴恰,就會觸發(fā)它們的getter斩熊,這樣就會把它們的dep添加到當(dāng)前watcher中往枣,使當(dāng)前計算屬性的watcher訂閱以來的響應(yīng)式屬性的變化。

一旦計算屬性依賴的數(shù)據(jù)被修改,就會觸發(fā) setter分冈,執(zhí)行watcher.update()方法通知watcher更新圾另。

  update () {
    /* istanbul ignore else */
    if (this.computed) {
      // A computed property watcher has two modes: lazy and activated.
      // It initializes as lazy by default, and only becomes activated when
      // it is depended on by at least one subscriber, which is typically
      // another computed property or a component's render function.
      if (this.dep.subs.length === 0) {
        // In lazy mode, we don't want to perform computations until necessary,
        // so we simply mark the watcher as dirty. The actual computation is
        // performed just-in-time in this.evaluate() when the computed property
        // is accessed.
        this.dirty = true
      } else {
        // In activated mode, we want to proactively perform the computation
        // but only notify our subscribers when the value has indeed changed.
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

如果當(dāng)前計算屬性沒有被任何組件依賴,就僅僅把this.dirty設(shè)置為true雕沉。否則重新計算屬性值集乔,然后通知組件重新渲染。

偵聽屬性

偵聽屬性的初始化是在initWatch方法中:

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
···
`initWatch`方法對每個偵聽屬性的回調(diào)函數(shù)執(zhí)行`createWatcher(vm, key, handler)`坡椒。如果一個偵聽屬性有多個回調(diào)函數(shù)可以使用一個數(shù)組扰路。handler還可以是一個包含options的Object

function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}

`createWatcehr`最終會調(diào)用`vm.$watch`方法。`vm.$watch`是Vue原型上的方法倔叼,它是在執(zhí)行`stateMixin`時添加到原型上的汗唱。

export function stateMixin (Vue: Class<Component>) {
// ...
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
}

通過`vm.$watch`方法創(chuàng)建的watcher是一個user watcher。用來觀察Vue實例上的一個響應(yīng)式屬性的變化丈攒,在變化時執(zhí)行回調(diào)函數(shù)哩罪。它可以有`immediate`或`deep`的選項。

user watcher的并沒有很大的區(qū)別肥印,只有邏輯上的不同识椰,這里就不分析了。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末深碱,一起剝皮案震驚了整個濱河市腹鹉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敷硅,老刑警劉巖功咒,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绞蹦,居然都是意外死亡力奋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門幽七,熙熙樓的掌柜王于貴愁眉苦臉地迎上來景殷,“玉大人,你說我怎么就攤上這事澡屡≡持浚” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵驶鹉,是天一觀的道長绩蜻。 經(jīng)常有香客問我,道長室埋,這世上最難降的妖魔是什么办绝? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任伊约,我火速辦了婚禮,結(jié)果婚禮上屡律,老公的妹妹穿的比我還像新娘昔驱。我一直安慰自己疹尾,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布腋颠。 她就那樣靜靜地躺著吓笙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪絮蒿。 梳的紋絲不亂的頭發(fā)上叁鉴,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天幌墓,我揣著相機與錄音,去河邊找鬼蜡饵。 笑死胳施,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舞肆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼氨鹏,長吁一口氣:“原來是場噩夢啊……” “哼仆抵!你這毒婦竟也來了跟继?” 一聲冷哼從身側(cè)響起舔糖,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤金吗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后摇庙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遥缕,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡单匣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年户秤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸡号。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡膜蠢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出礁竞,到底是詐尸還是另有隱情,我是刑警寧澤模捂,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布狂男,位于F島的核電站品腹,受9級特大地震影響岖食,放射性物質(zhì)發(fā)生泄漏舞吭。R本人自食惡果不足惜析珊,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一忠寻、第九天 我趴在偏房一處隱蔽的房頂上張望奕剃。 院中可真熱鬧,春花似錦纵朋、人聲如沸叙量。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肘交,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涯呻,已是汗流浹背腻要。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工雄家, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趟济。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓戚炫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親媳纬。 傳聞我的和親對象是個殘疾皇子叛甫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容