欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue中computed和watch的區(qū)別

 更新時間:2023年05月18日 11:00:09   作者:Govi  
在vue項目中我們常常需要用到computed和watch,那么我們究竟在什么場景下使用computed和watch呢?他們之間又有什么區(qū)別呢?本將給大家詳細(xì)的介紹一下,需要的朋友可以參考下

前言??

在vue項目中我們常常需要用到computed和watch,那么我們究竟在什么場景下使用computed和watch呢?他們之間又有什么區(qū)別呢?記錄一下!

computed和watch有什么區(qū)別?

相同點:(過目一下,下面還會更新)

  • 本質(zhì)上都是一個watcher實例,它們都通過響應(yīng)式系統(tǒng)與數(shù)據(jù),頁面建立通信
  • 它們都是以Vue的依賴追蹤機(jī)制為基礎(chǔ)的

computed

簡而言之,它的作用就是自動計算我們定義在函數(shù)內(nèi)的“公式”

 data() {
    return {
      num1: 1,
      num2: 2
    };
  },
  computed: {
    total() {
      return this.num1 * this.num2;
    }
  }

在這個場景下,當(dāng)this.num1或者this.num2變化時,這個total的值也會隨之變化,為什么呢?

## 計算屬性實現(xiàn):

computed是一個函數(shù)可以看出,它應(yīng)該也有一個初始化函數(shù) initComputed來對它進(jìn)行初始化。

  • 從vue源碼可以看出在initState函數(shù)中對computed進(jìn)行初始化,往下看

  • initComputed函數(shù)中,有兩個參數(shù),vm為vue實例,computed就是我們所定義的computed

  • 具體實現(xiàn)邏輯就不具體解析了,從上面源碼中可以發(fā)現(xiàn),initComputed函數(shù)會遍歷我們定義的computed對象,然后給每一個值綁定一個watcher實例

  • Watcher實例是響應(yīng)式系統(tǒng)中負(fù)責(zé)監(jiān)聽數(shù)據(jù)變化的角色

  • 計算屬性執(zhí)行的時候就會被訪問到,this.num1和this.num2在Data初始化的時候就被定義成響應(yīng)式數(shù)據(jù)了,它們內(nèi)部會有一個Dep實例,Dep實例就會把這個計算屬性watcher放到自己的sub數(shù)組內(nèi),往后如果子級更新了,就會通知數(shù)組內(nèi)的watcher實例更新

  • 再看回源碼

const computedWatcherOptions = { lazy: true }

// vm: 組件實例 computed 組件內(nèi)的 計算屬性對象
function initComputed (vm: Component, computed: Object) {
  // 遍歷所有的計算屬性
  for (const key in computed) {
    // 用戶定義的 computed
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    watchers[key] = new Watcher( 
      vm,
      getter || noop,
      noop,
      computedWatcherOptions
    )
 
  defineComputed(vm, key, userDef)
}

  • 可以看出在watcher實例在剛被創(chuàng)建時就往ComputedWatcherOptions, 傳了{ lazy: true }, 即意味著它不會立即執(zhí)行我們定義的計算屬性函數(shù),這也意味著它是一個懶計算的功能(標(biāo)記一下)

  • 說到這,就能基本了解了計算watcher實例在計算屬性執(zhí)行流程的作用了,即初始化的過程,那么計算屬性是怎么執(zhí)行的?

  • 從上面的源碼可以看出最下面還有一個defineComputed函數(shù),它到底是干嘛的?其實它是vue中用來判斷computed中的key是否已經(jīng)在實例中定義過,如果未定義,則執(zhí)行defineComputed函數(shù)

  • 來看一下defineComputed函數(shù)

  • 可以看出這里截取了兩個函數(shù),defineComputedcreateComputedGetter兩個函數(shù)

首先說說defineComputed函數(shù)

  • 它會判斷是否為服務(wù)器渲染,如果為服務(wù)器渲染則將計算屬性的get、set定義為用戶定義get、set;怎么理解?如果非服務(wù)器渲染的話則在定義get屬性的時候并沒有直接賦值用戶函數(shù),而是返回一個新的函數(shù)computedGetter
  • 這里會判斷userDef也就是用戶定義計算屬性key對應(yīng)的value值是否為函數(shù),如果為函數(shù)的話,則將get定義為用戶函數(shù),set賦值為一個空函數(shù)noop;如果不為函數(shù)(對象)則分別取get、set字段賦值
  • 非服務(wù)端渲染中計算屬性的get屬性為computedGetter函數(shù),在每次計算屬性觸發(fā)get屬性時,都會從實例的_computedWatchers(在initComputed已初始化)計算屬性的watcher對象中獲取get函數(shù)(用戶定義函數(shù))
  • 至此,計算屬性的初始化就結(jié)束了,最終會把當(dāng)前key定義到vue實例上,也就是可以this.computedKey可以獲取到的原因
  • 細(xì)心的同學(xué)可能發(fā)現(xiàn)了,在上述源碼中還有一行代碼 :Object.defineProperty(target, key, sharedPropertyDefinition),它就是我接下來要說的defineComputed函數(shù)做的第二件事(第一件事就是上面的操作)。當(dāng)訪問一次計算屬性的key 就會觸發(fā)一次 sharedPropertyDefinition(我們自定義的函數(shù)),對computed做了一次劫持,Target可以理解為this,從上面源碼可以看出,每次使用計算屬性,都會執(zhí)行一次computedGetter,跟我們一開始的DEMO一樣,它就會執(zhí)行我們定義的函數(shù),具體怎么實現(xiàn)?

function computedGetter () {
    // 拿到 上述 創(chuàng)建的 watcher 實例
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 首次執(zhí)行的時候 dirty 基于 lazy 所以是true
      if (watcher.dirty) {
        // 這個方法會執(zhí)行一次計算
        // dirty 設(shè)置為 false
        // 這個函數(shù)執(zhí)行完畢后, 當(dāng)前 計算watcher就會推出
        watcher.evaluate()
      }
      // 如果當(dāng)前激活的渲染watcher存在
      if (Dep.target) {
        /**
         * evaluate后求值的同時, 如果當(dāng)前 渲染watcher 存在,
         * 則通知當(dāng)前的收集了 計算watcher 的 dep 收集當(dāng)前的 渲染watcher
         *
         *    為什么要這么做?
         * 假設(shè)這個計算屬性是在模板中被使用的, 并且渲染watcher沒有被對應(yīng)的dep收集
         * 那派發(fā)更新的時候, 計算屬性依賴的值發(fā)生改變, 而當(dāng)前渲染watcher不被更新
         * 就會出現(xiàn), 頁面中的計算屬性值沒有發(fā)生改變的情況.
         *
         * 本質(zhì)上計算屬性所依賴的dep, 也可以看做這個屬性值本身的dep實例.
         */
        watcher.depend()
      }
      return watcher.value
    }
  }

  • 綜上所述,更加證實了文章開頭所說的計算屬性帶有“懶計算”的功能,為什么呢?回看上面的代碼中的watcher.dirty,在**計算watcher實例化的時候,一開始watcher.dirty會被設(shè)置為true**,這樣一說,上面所說的邏輯好像能走通了。

  • 走到這里會執(zhí)行watcher的evaluate(),即求值,this.get()簡單理解為執(zhí)行我們定義的計算屬性函數(shù)就可以了。

evaluate () {
    this.value = this.get()
    this.dirty = false
  }
  • this.dirty 這時候就被變成false

  • 既然這樣,我們是不是可以理解為當(dāng)this.dirtyfalse時就不會執(zhí)行這個函數(shù)。Vue為什么這樣做? 當(dāng)然是覺得, 它依賴的值沒有變化, 就沒有計算的必要啦

  • 那么問題來了,說了這么久,我們只看到了將this.dirty設(shè)為false,什么時候設(shè)為true呢?來看一下響應(yīng)式系統(tǒng)set部分代碼

set: function reactiveSetter (newVal) {
  const value = getter ? getter.call(obj) : val
  
  if (newVal === value || (newVal !== newVal && value !== value)) {
    return
  }

  // 通知它的訂閱者更新
  dep.notify()
}


  • 這段代碼只做兩件事:

    1.如果新值和舊值一致,則無需做任何事。

    2.如果新值和舊值不一致,則通知這個數(shù)據(jù)下的訂閱者,也就是watcher實例更新

  • Notity方法就是遍歷一下它的數(shù)組,然后執(zhí)行數(shù)組里每個watcherupdate方法

update () {
    /* istanbul ignore else */
    if (this.lazy) {
      // 假設(shè)當(dāng)前 發(fā)布者 通知 值被重新 set
      // 則把 dirty 設(shè)置為 true 當(dāng)computed 被使用的時候 就可以重新調(diào)用計算
      // 渲染wacher 執(zhí)行完畢 堆出后, 會輪到當(dāng)前的渲染watcher執(zhí)行update
      // 此時就會去執(zhí)行queueWatcher(this), 再重新執(zhí)行 組件渲染時候
      // 會用到計算屬性, 在這時因為 dirty 為 true 所以能重新求值
      // dirty就像一個閥門, 用于判斷是否應(yīng)該重新計算
      this.dirty = true
    }
  }


  • 就在這里,**dirty**被重新設(shè)置為了**true**.
  • 總結(jié)一下dirty的流程:

一開始dirtytrue,一旦執(zhí)行了一次計算,就會設(shè)置為false,然后當(dāng)它定義的函數(shù)內(nèi)部依賴的值發(fā)生了變化,則這個值就會重新變?yōu)?strong>true。怎么理解?就拿上面的this.num1this.num2來說,當(dāng)二者其中一個變化了,dirty的值就變?yōu)?strong>true。

  • 說了這么久dirty,那它到底有什么作用?簡而言之,它就是用來記錄我們依賴的值有沒有變,如果變了就重新計算一下值,如果沒變,那就返回以前的值。就像一個懶加載的理念,這也是計算屬性緩存的一種方式。有聰明的同學(xué)又會問了,我們好像一直在讓dirty變成true |false,好像實現(xiàn)邏輯完全跟緩存搭不著邊,也完全沒有涉及到計算屬性函數(shù)的執(zhí)行呀?那我們回頭看看computedGetter函數(shù)
function computedGetter () {
    // 拿到 上述 創(chuàng)建的 watcher 實例
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 首次執(zhí)行的時候 dirty 基于 lazy 所以是true
      if (watcher.dirty) {
        // 這個方法會執(zhí)行一次計算
        // dirty 設(shè)置為 false
        // 這個函數(shù)執(zhí)行完畢后, 當(dāng)前 計算watcher就會推出
        watcher.evaluate()
      }
      // 如果當(dāng)前激活的渲染watcher存在
      if (Dep.target) {
        /**
         * evaluate后求值的同時, 如果當(dāng)前 渲染watcher 存在,
         * 則通知當(dāng)前的收集了 計算watcher 的 dep 收集當(dāng)前的 渲染watcher
         *
         *    為什么要這么做?
         * 假設(shè)這個計算屬性是在模板中被使用的, 并且渲染watcher沒有被對應(yīng)的dep收集
         * 那派發(fā)更新的時候, 計算屬性依賴的值發(fā)生改變, 而當(dāng)前渲染watcher不被更新
         * 就會出現(xiàn), 頁面中的計算屬性值沒有發(fā)生改變的情況.
         *
         * 本質(zhì)上計算屬性所依賴的dep, 也可以看做這個屬性值本身的dep實例.
         */
        watcher.depend()
      }
      return watcher.value
    }
  }

  • 這里有一段 Dep.target 的判斷邏輯. 這是什么意思呢. Dep.target當(dāng)前正在渲染組件. 它代指的是你定義的組件, 它也是一個**watcher**, 我們一般稱之為**渲染watcher**.

    計算屬性watcher, 被通知更新的時候, 會改變**dirty的值. 而渲染watcher**被通知更新的時候, 它就會更新一次頁面.

    顯然我們現(xiàn)在的問題是, 計算屬性的**dirty重新變?yōu)?/strong>ture了, 怎么讓頁面知道現(xiàn)在要重新刷新**了呢?

    通過**watcher.depend()** 這個方法會通知當(dāng)前數(shù)據(jù)的**Dep實例去收集我們的渲染watcher. 將其收集起來.當(dāng)數(shù)據(jù)發(fā)生變化的時候, 首先通知計算watcher更改drity值, 然后通知渲染watcher更新頁面. 渲染watcher更新頁面的時候, 如果在頁面的HTML結(jié)果中我們用到了total這個屬性. 就會觸發(fā)它對應(yīng)的computedGetter方法. 也就是執(zhí)行上面這部分代碼. 這時候drityture, 就能如期執(zhí)行watcher.evaluate()**方法了。

  • 至此,computed屬性的邏輯已經(jīng)完畢,總結(jié)來說就是:computed屬性緩存功能,實際上是通過一個dirty字段作為節(jié)流閥實現(xiàn)的,如果需要重新求值,閥門就打開,否則就一直返回原先的值,而無需重新計算。

watch

watch更多充當(dāng)監(jiān)控者的角色

  • 先看例子,當(dāng)total發(fā)生變化時,handler函數(shù)就會被執(zhí)行。
data() {
    return {
        total:99
    }
},
watch: {
    count: {
        hanlder(){
            console.log('total改變了')
        }
    }
}


  • 相同道理,在watch初始化的時候,肯定有一個initWatch函數(shù),來初始化我們的監(jiān)聽屬性,來到源碼
// src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
  // 遍歷我們定義的wathcer
  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)
    }
  }
}

  • 不難看出,當(dāng)這個函數(shù)拿到我們所定義的watch對象total對象,然后拿到handler值,當(dāng)然handler也可以是一個數(shù)組,然后傳進(jìn)createWatcher函數(shù)中,那么在這個過程中又做了什么呢?接著看
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)
}

  • 看得出來,它會解析我們傳進(jìn)來的handler對象,最后調(diào)用**$watch**實現(xiàn)監(jiān)聽,當(dāng)然我們也可以直接通過這個方法實現(xiàn)監(jiān)聽。為什么呢?接著看
Vue.prototype.$watch = function (
    expOrFn: string | Function, // 這個可以是 key
    cb: any, // 待執(zhí)行的函數(shù)
    options?: Object // 一些配置
  ): Function {
    const vm: Component = this
    // 創(chuàng)建一個 watcher 此時的 expOrFn 是監(jiān)聽對象
    const watcher = new Watcher(vm, expOrFn, cb, options)
    
    return function unwatchFn () {
      watcher.teardown()
    }
  }

  • 從代碼看的出來,watch函數(shù)∗∗是Vue實例原型上的一個方法,那么我們就可以通過∗∗this∗∗的形式去調(diào)用它。而∗∗watch函數(shù)**是Vue實例原型上的一個方法,那么我們就可以通過**this**的形式去調(diào)用它。而**watch函數(shù)∗∗是Vue實例原型上的一個方法,那么我們就可以通過∗∗this∗∗的形式去調(diào)用它。而∗∗watch屬性就實例化了一個watcher對象,然后通過這個watcher實現(xiàn)了監(jiān)聽,這就是為什么watchcomputed本質(zhì)上都是一個watcher對象的原因。那既然它跟computed都是watcher實例,那么本質(zhì)上都是通過Vue響應(yīng)式系統(tǒng)實現(xiàn)的監(jiān)聽,那是不容置疑的。好,到這里我們就要想一個問題,total的Dep實例,是什么時候收集這個watcher實例的?回看實例化時的代碼
Vue.prototype.$watch = function (
    expOrFn: string | Function, 
    cb: any,
    options?: Object
  )


  • vm是組件實例, 也就是我們常用的this
  • expOrFn是在我們的Demo中就是total, 也就是被監(jiān)聽的屬性
  • cb就是我們的handler函數(shù)
if (typeof expOrFn === 'function') {
  this.getter = expOrFn
} else {
  // 如果是一個字符則轉(zhuǎn)為一個 一個 getter 函數(shù)
  // 這里這么做是為了通過 this.[watcherKey] 的形式
  // 能夠觸發(fā) 被監(jiān)聽屬性的 依賴收集
  this.getter = parsePath(expOrFn)
  if (!this.getter) {
    this.getter = noop
    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
    )
  }
}
this.value = this.lazy
  ? undefined
  : this.get()

  • 這是**watcher實例化的時候, 會默認(rèn)執(zhí)行的一串代碼, 回想一下我們在computed實例化的時候傳入的函數(shù), 也是expOrFn.** 如果是一個函數(shù)會被直接賦予. 如果是一個字符串. 則**parsePath通過創(chuàng)建為一個函數(shù). 大家不需要關(guān)注這個函數(shù)的行為, 它內(nèi)部就是執(zhí)行一次this.[expOrFn]. 也就是this.total**

  • 最后, 因為**lazyfalse. 這個值只有計算屬性的時候才會被傳true.所以首次會執(zhí)行this.get()**. get里面則是執(zhí)行一次getter()觸發(fā)響應(yīng)式

  • 到這里監(jiān)聽屬性的初始化邏輯就算是完成了, 但是在數(shù)據(jù)更新的時候, 監(jiān)聽屬性的觸發(fā)還有與計算屬性不一樣的地方.

  • 監(jiān)聽屬性是異步觸發(fā)的,為什么呢?因為監(jiān)聽屬性的執(zhí)行邏輯和組件的渲染是一樣的,他們都會放到一個nextTick函數(shù)中,放到下一次Tick中執(zhí)行

總結(jié)

說了這么多關(guān)于這兩座大山的相關(guān)內(nèi)容,也該來總結(jié)一下了。

相同點:

  • 本質(zhì)上都是一個watcher實例,它們都通過響應(yīng)式系統(tǒng)與數(shù)據(jù),頁面建立通信,只是行為不同
  • 計算屬性和監(jiān)聽屬性對于新值和舊值一樣的賦值操作,都不會做任何變化,不過這一點的實現(xiàn)是在響應(yīng)式系統(tǒng)完成的。
  • 它們都是以Vue的依賴追蹤機(jī)制為基礎(chǔ)的

不同點:

  • 計算屬性具有“懶計算”功能,只有依賴的值變化了,才允許重新計算,成為"緩存",感覺不夠準(zhǔn)確。
  • 在數(shù)據(jù)更新時,計算屬性的dirty狀態(tài)會立即改變,而監(jiān)聽屬性與組件重新渲染,至少會在下一個"Tick"執(zhí)行。

#感謝

至此,本篇有關(guān)computed和watch屬性的相關(guān)內(nèi)容到此就結(jié)束啦,有什么補(bǔ)充的可以聯(lián)系我哦!

以上就是Vue中computed和watch的區(qū)別的詳細(xì)內(nèi)容,更多關(guān)于Vue computed和watch的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue-父子組件和ref實例詳解

    vue-父子組件和ref實例詳解

    這篇文章通過實例代碼給大家介紹了vue-父子組件傳值和ref獲取dom和組件的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-11-11
  • vue使用echarts實現(xiàn)地圖的方法詳解

    vue使用echarts實現(xiàn)地圖的方法詳解

    這篇文章主要為大家詳細(xì)介紹了vue使用echarts實現(xiàn)地圖的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Vue2 cube-ui時間選擇器詳解

    Vue2 cube-ui時間選擇器詳解

    這篇文章主要為大家介紹了Vue2 cube-ui時間選擇器,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12
  • Vue 實現(xiàn)雙向綁定的四種方法

    Vue 實現(xiàn)雙向綁定的四種方法

    這篇文章主要介紹了Vue 實現(xiàn)雙向綁定的四種方法,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2018-03-03
  • 討論vue中混入mixin的應(yīng)用

    討論vue中混入mixin的應(yīng)用

    這篇文章主要介紹了vue中混入mixin的理解和應(yīng)用,對vue感興趣的同學(xué),可以參考下
    2021-05-05
  • vue實現(xiàn)元素拖動并互換位置的實現(xiàn)代碼

    vue實現(xiàn)元素拖動并互換位置的實現(xiàn)代碼

    在使用Vue的場景下,需要實現(xiàn)對元素進(jìn)行拖動交換位置,接下來通過本文給大家介紹vue實現(xiàn)元素拖動并互換位置的實現(xiàn)代碼,需要的朋友可以參考下
    2023-09-09
  • 解決ElementUI中tooltip出現(xiàn)無法顯示的問題

    解決ElementUI中tooltip出現(xiàn)無法顯示的問題

    這篇文章主要介紹了解決ElementUI中tooltip出現(xiàn)無法顯示的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • vue中v-for數(shù)據(jù)狀態(tài)值變了,但是視圖沒改變的解決方案

    vue中v-for數(shù)據(jù)狀態(tài)值變了,但是視圖沒改變的解決方案

    這篇文章主要介紹了vue中v-for數(shù)據(jù)狀態(tài)值變了,但是視圖沒改變的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • vue2如何使用vue-i18n搭建多語言切換環(huán)境

    vue2如何使用vue-i18n搭建多語言切換環(huán)境

    這篇文章主要介紹了vue2-使用vue-i18n搭建多語言切換環(huán)境的相關(guān)知識,在data(){}中獲取的變量存在更新this.$i18n.locale的值時無法自動切換的問題,需要刷新頁面才能切換語言,感興趣的朋友一起看看吧
    2023-12-12
  • vue?el-table實現(xiàn)動態(tài)添加行和列具體代碼

    vue?el-table實現(xiàn)動態(tài)添加行和列具體代碼

    最近遇到一個動態(tài)增加行和列的需求,所以這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于vue?el-table實現(xiàn)動態(tài)添加行和列的相關(guān)資料,需要的朋友可以參考下
    2023-09-09

最新評論