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

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

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

前言??

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

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

相同點(diǎn):(過(guò)目一下,下面還會(huì)更新)

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

computed

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

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

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

## 計(jì)算屬性實(shí)現(xiàn):

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

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

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

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

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

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

  • 再看回源碼

const computedWatcherOptions = { lazy: true }

// vm: 組件實(shí)例 computed 組件內(nèi)的 計(jì)算屬性對(duì)象
function initComputed (vm: Component, computed: Object) {
  // 遍歷所有的計(jì)算屬性
  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實(shí)例在剛被創(chuàng)建時(shí)就往ComputedWatcherOptions, 傳了{ lazy: true }, 即意味著它不會(huì)立即執(zhí)行我們定義的計(jì)算屬性函數(shù),這也意味著它是一個(gè)懶計(jì)算的功能(標(biāo)記一下)

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

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

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

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

首先說(shuō)說(shuō)defineComputed函數(shù)

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

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

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

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

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

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

  • 那么問(wèn)題來(lái)了,說(shuō)了這么久,我們只看到了將this.dirty設(shè)為false,什么時(shí)候設(shè)為true呢?來(lái)看一下響應(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.如果新值和舊值一致,則無(wú)需做任何事。

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

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

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


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

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

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

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

    計(jì)算屬性watcher, 被通知更新的時(shí)候, 會(huì)改變**dirty的值. 而渲染watcher**被通知更新的時(shí)候, 它就會(huì)更新一次頁(yè)面.

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

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

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

watch

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

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


  • 相同道理,在watch初始化的時(shí)候,肯定有一個(gè)initWatch函數(shù),來(lái)初始化我們的監(jiān)聽(tīng)屬性,來(lái)到源碼
// 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)這個(gè)函數(shù)拿到我們所定義的watch對(duì)象total對(duì)象,然后拿到handler值,當(dāng)然handler也可以是一個(gè)數(shù)組,然后傳進(jìn)createWatcher函數(shù)中,那么在這個(gè)過(guò)程中又做了什么呢?接著看
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)
}

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

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


  • vm是組件實(shí)例, 也就是我們常用的this
  • expOrFn是在我們的Demo中就是total, 也就是被監(jiān)聽(tīng)的屬性
  • cb就是我們的handler函數(shù)
if (typeof expOrFn === 'function') {
  this.getter = expOrFn
} else {
  // 如果是一個(gè)字符則轉(zhuǎn)為一個(gè) 一個(gè) getter 函數(shù)
  // 這里這么做是為了通過(guò) this.[watcherKey] 的形式
  // 能夠觸發(fā) 被監(jiān)聽(tīng)屬性的 依賴收集
  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實(shí)例化的時(shí)候, 會(huì)默認(rèn)執(zhí)行的一串代碼, 回想一下我們?cè)?/strong>computed實(shí)例化的時(shí)候傳入的函數(shù), 也是expOrFn.** 如果是一個(gè)函數(shù)會(huì)被直接賦予. 如果是一個(gè)字符串. 則**parsePath通過(guò)創(chuàng)建為一個(gè)函數(shù). 大家不需要關(guān)注這個(gè)函數(shù)的行為, 它內(nèi)部就是執(zhí)行一次this.[expOrFn]. 也就是this.total**

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

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

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

總結(jié)

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

相同點(diǎn):

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

不同點(diǎn):

  • 計(jì)算屬性具有“懶計(jì)算”功能,只有依賴的值變化了,才允許重新計(jì)算,成為"緩存",感覺(jué)不夠準(zhǔn)確。
  • 在數(shù)據(jù)更新時(shí),計(jì)算屬性的dirty狀態(tài)會(huì)立即改變,而監(jiān)聽(tīng)屬性與組件重新渲染,至少會(huì)在下一個(gè)"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的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論