最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3計(jì)算屬性是如何實(shí)現(xiàn)的

 更新時(shí)間:2022年08月30日 11:23:43   作者:咕咕雞_  
這篇文章主要介紹了Vue3計(jì)算屬性是如何實(shí)現(xiàn)的,對于任何包含響應(yīng)式數(shù)據(jù)的復(fù)雜邏輯,我們都應(yīng)該使用計(jì)算屬性,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下

前言:

本篇內(nèi)容基于Vue3響應(yīng)式對象是如何實(shí)現(xiàn)的(2)實(shí)現(xiàn)。

計(jì)算屬性

Vue3的官方文檔中,對于計(jì)算屬性有這樣的描述:

  • 對于任何包含響應(yīng)式數(shù)據(jù)的復(fù)雜邏輯,我們都應(yīng)該使用計(jì)算屬性。
  • 計(jì)算屬性只會(huì)在相關(guān)響應(yīng)式依賴發(fā)生改變時(shí)重新求值。

從上面的描述可以明確計(jì)算屬性的需求,計(jì)算屬性計(jì)算的是響應(yīng)式數(shù)據(jù)(滿足描述1),且計(jì)算結(jié)果應(yīng)當(dāng)被緩存(滿足描述2)。讓我們一個(gè)一個(gè)來實(shí)現(xiàn),先使用computed創(chuàng)建一個(gè)計(jì)算屬性。

function effect(fn) { // 副作用函數(shù)
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  effectFn.deps = [] 
  effectFn()
}
...
const data = { foo: 1, bar: 2 }
const obj = new Proxy(data, { // 響應(yīng)式對象
  get(target, key) {
    track(target, key)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    trigger(target, key)
    return true
  }
})
...
const sumRes = computed(() => obj.foo + obj.bar) // (1)
console.log(sumRes.value)

在(1)處,我們簡單寫了一個(gè)計(jì)算屬性的功能,為了實(shí)現(xiàn)通過sumRes.value讀取計(jì)算屬性值功能,在實(shí)現(xiàn)計(jì)算屬性時(shí),需要返回一個(gè)對象,通過對象內(nèi)的get觸發(fā)副作用函數(shù)。

function computed(getter) {
  const effectFn = effect(getter)
  const obj = {
    get value() {
      return effectFn()
    }
  }
  return obj
}

但這個(gè)函數(shù)顯然是無法執(zhí)行的,這是因?yàn)榍懊嫖覀冊趯?shí)現(xiàn)effect時(shí),需要直接執(zhí)行副作用函數(shù),不需要提供返回值。沒有返回值,computed自然無法獲取到effect的執(zhí)行結(jié)果。因此,當(dāng)在計(jì)算屬性中使用effect時(shí),需要將副作用函數(shù)返回給計(jì)算屬性,由計(jì)算屬性決定何時(shí)執(zhí)行,而不再由effect立即執(zhí)行(即懶執(zhí)行)。

為了實(shí)現(xiàn)這點(diǎn),就需要向effect中添加一個(gè)開關(guān)lazy,考慮到我們可能將來還需要對effect配置其它特性,我們使用一個(gè)對象options來封裝這個(gè)開關(guān)。

function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    const res = fn() // (1)
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    return res // (2)
  }
  effectFn.deps = []
  effectFn.options = options // (3)
  if (!options.lazy) { // (4)
    effectFn()
  }
  return effectFn // (5)
}

我們在(4)處放置了lazy開關(guān),不需要懶執(zhí)行的副作用函數(shù)同樣會(huì)自動(dòng)執(zhí)行。在(1)(2)(5)處返回了副作用函數(shù)的結(jié)果,供懶執(zhí)行使用。同時(shí)在(3)處向下傳遞了options,保證在effect發(fā)生嵌套時(shí),也使得副作用函數(shù)執(zhí)行預(yù)期的行為。基于上述effect的修改,我們在computed中設(shè)置lazy開關(guān)。

function computed(getter) {
  const effectFn = effect(getter, { lazy: true })
  const obj = {
    get value() { // (6)
      return effectFn()
    }
  }
  return obj
}
const sumRes = computed(() => obj.foo + obj.bar)

從上圖中可以看出,我們已經(jīng)實(shí)現(xiàn)了描述1,即使用計(jì)算屬性進(jìn)行響應(yīng)式數(shù)據(jù)的計(jì)算,當(dāng)響應(yīng)式數(shù)據(jù)的值發(fā)生變化時(shí),計(jì)算屬性的值也會(huì)隨之改變。但觀察上文代碼的(6)處,不難發(fā)現(xiàn),無論什么情況下,只要讀取sumRes.value的值,就會(huì)觸發(fā)一次副作用函數(shù),使其重新進(jìn)行可能不必要的執(zhí)行。所以接著,我們嘗試實(shí)現(xiàn)描述2,緩存計(jì)算屬性的結(jié)果。

先從最簡單的入手,我們用一個(gè)變量value來緩存上次計(jì)算的值,并添加一個(gè)dirty開關(guān),記錄是否需要重新觸發(fā)副作用函數(shù)。

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, { lazy: true })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      return value
    }
  }
  return obj
}

修改之后,緩存的值就能生效了。但這樣做產(chǎn)生了一個(gè)明顯的BUG,當(dāng)dirty的值被置為false時(shí),無法再變?yōu)?code>true,這也就意味著,無論響應(yīng)式數(shù)據(jù)obj.barobj.foo如何變化,計(jì)算屬性的值永遠(yuǎn)都只能是緩存的值value,如下圖所示。

為了解決這個(gè)問題,我們需要一種方式,能夠在obj.barobj.foo的值變化時(shí),在獲取sumRes.value之前,將dirty開關(guān)的值置為true。受前面懶加載的啟發(fā),我們嘗試能不能通過配置options來實(shí)現(xiàn)這個(gè)功能。

const obj = new Proxy(data, {
  get(target, key) {
    track(target, key)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    trigger(target, key)
    return true
  }
})

再來回憶一下響應(yīng)式對象的整個(gè)流程,當(dāng)響應(yīng)式對象中的數(shù)據(jù)被修改時(shí),執(zhí)行了trigger去觸發(fā)收集的副作用函數(shù)。而在計(jì)算屬性中,我們不再需要自動(dòng)的觸發(fā)副作用函數(shù)。所以自然會(huì)想到,能否在這個(gè)地方將dirty置為true呢?按照這個(gè)思路,我們先對trigger進(jìn)行修改。

function trigger(target, key) {
  const propsMap = objsMap.get(target)
  if(!propsMap) return
  const fns = propsMap.get(key)
  const otherFns = new Set()
  fns && fns.forEach(fn => {
    if(fn !== activeEffect) {
      otherFns.add(fn)
    }
  })
  otherFns.forEach(fn => {
    if(fn.options.scheduler) { // (7)
      fn.options.scheduler()
    } else {
      fn()
    }
  })
}

按照前文的思路,我們在(7)處增加了一個(gè)判斷,如果副作用函數(shù)fn的配置項(xiàng)options中含有scheduler函數(shù),我們就執(zhí)行scheduler而非副作用函數(shù)fn。我們稱這里的scheduler調(diào)度器。相應(yīng)的,我們在計(jì)算屬性中添加調(diào)度器。

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, { 
    lazy: true,
    scheduler() { // (8)
      dirty = true
    } 
  })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      return value
    }
  }
  return obj
}

在(8)處我們添加了調(diào)度器。添加調(diào)度器后,讓我們再來串一下整個(gè)流程,當(dāng)響應(yīng)式數(shù)據(jù)被修改時(shí),才會(huì)執(zhí)行trigger函數(shù)。由于我們傳入了調(diào)度器,因此trigger函數(shù)在執(zhí)行時(shí)不再觸發(fā)副作用函數(shù),轉(zhuǎn)而執(zhí)行調(diào)度器,此時(shí)dirty開關(guān)的值變?yōu)榱?code>true。當(dāng)程序再往下執(zhí)行時(shí),由于dirty已經(jīng)變?yōu)?code>true,副作用函數(shù)就可以正常被手動(dòng)觸發(fā)。效果如下圖所示。

到這里為止,計(jì)算屬性在功能上已經(jīng)實(shí)現(xiàn)完畢了,讓我們嘗試完善它。在Vue中,當(dāng)計(jì)算屬性中的響應(yīng)式數(shù)據(jù)被修改時(shí),計(jì)算屬性值會(huì)同步更改,進(jìn)而重新渲染,并在頁面上更新。渲染函數(shù)也會(huì)執(zhí)行effect,為了說明問題,讓我們使用effect簡單的模擬一下。

const sumRes = computed(() => obj.foo + obj.bar)
effect(() => console.log('sumRes =', sumRes.value))

這里我們的預(yù)期是當(dāng)obj.fooobj.bar改變時(shí),effect會(huì)自動(dòng)重新執(zhí)行。這里存在的問題是,正常的effect嵌套可以被自動(dòng)觸發(fā)(這點(diǎn)我們在上一篇博客中已經(jīng)實(shí)現(xiàn)了),但sumRes包含的effect僅會(huì)在被讀取value時(shí)才會(huì)被get觸發(fā)執(zhí)行,這就導(dǎo)致響應(yīng)式數(shù)據(jù)obj.fooobj.bar無法收集到外部的effect,收集不到的副作用函數(shù),自然無法被自動(dòng)觸發(fā)。

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, { 
    lazy: true,
    scheduler() {
      dirty = true
      trigger(obj, 'value') // (9)
    } 
  })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value') // (10)
      return value
    }
  }
  return obj
}

在(10)處我們手動(dòng)收集了副作用函數(shù),并當(dāng)響應(yīng)式數(shù)據(jù)被修改時(shí),觸發(fā)它們。

使用微任務(wù)優(yōu)化調(diào)度器

在設(shè)計(jì)調(diào)度器時(shí),我們忽略了一個(gè)潛在的問題。

還是先來看一個(gè)例子:

effect(() => console.log(obj.foo))
for(let i = 0; i < 1e5; i++) {
  obj.foo++
}

隨著響應(yīng)式數(shù)據(jù)obj.foo被不停修改,副作用函數(shù)也被不斷重復(fù)執(zhí)行,在明顯的延遲之后,控制臺(tái)打印出了大量的數(shù)據(jù)。但在前端的實(shí)際開發(fā)中,我們往往只關(guān)心最終結(jié)果,拿到結(jié)果顯示在頁面上。在這種條件下,我們?nèi)绾伪苊飧弊饔煤瘮?shù)被重復(fù)執(zhí)行呢?

const jobQueue = new Set() // (11)
const p = Promise.resolve() // (12)
let isFlushing = false // (13)
function flushJob() { // (14)
  if (isFlushing) return
  isFlushing = true
  p.then(() => {
    jobQueue.forEach(job => {
      job()
    })
  }).finally(() => {
    isFlushing = false
  })
}

這里我們的思路是使用微任務(wù)隊(duì)列來進(jìn)行優(yōu)化。(11)處我們定義了一個(gè)Set作為任務(wù)隊(duì)列,(12)處我們定義了一個(gè)Promise來使用微任務(wù)。精彩的部分來了,我們的思路是將副作用函數(shù)放入任務(wù)隊(duì)列中,由于任務(wù)隊(duì)列是基于Set實(shí)現(xiàn)的,因此,重復(fù)的副作用函數(shù)僅會(huì)保留一個(gè),這是第一點(diǎn)。接著,我們執(zhí)行flushJob(),這里我們巧妙的設(shè)置了一個(gè)isFlushing開關(guān),這個(gè)開關(guān)保證了被微任務(wù)包裹的任務(wù)隊(duì)列在一次事件循環(huán)中只會(huì)執(zhí)行一次,而執(zhí)行的這一次會(huì)在宏任務(wù)完成之后觸發(fā)任務(wù)隊(duì)列中所有不重復(fù)的副作用函數(shù),從而直接拿到最終結(jié)果,這是第二點(diǎn)。按照這個(gè)思路,我們在effect中添加調(diào)度器。

effect(() => { console.log(obj.foo) }, {
  scheduler(fn) {
    jobQueue.add(fn)
    flushJob()
  }
})

需要注意的是,瀏覽器在執(zhí)行微任務(wù)時(shí),會(huì)把微任務(wù)隊(duì)列中的所有微任務(wù)一口氣做完。因此,微任務(wù)在執(zhí)行時(shí)會(huì)阻塞頁面的渲染。所以在實(shí)際使用時(shí),要注意避免在微任務(wù)隊(duì)列中放置過重的任務(wù),以免引起卡頓。

到此這篇關(guān)于Vue3計(jì)算屬性是如何實(shí)現(xiàn)的的文章就介紹到這了,更多相關(guān)Vue3計(jì)算屬性 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue多環(huán)境配置之 .env配置文件詳解

    vue多環(huán)境配置之 .env配置文件詳解

    .env文件是vue運(yùn)行項(xiàng)目時(shí)的環(huán)境配置文件,這篇文章主要介紹了vue多環(huán)境配置之 .env配置文件,需要的朋友可以參考下
    2023-03-03
  • vue3組合式API實(shí)現(xiàn)todo列表效果

    vue3組合式API實(shí)現(xiàn)todo列表效果

    這篇文章主要介紹了vue3組合式API實(shí)現(xiàn)todo列表,下面用組合式?API的寫法,實(shí)現(xiàn)一個(gè)可新增、刪除的todo列表效果,需要的朋友可以參考下
    2024-08-08
  • vuex實(shí)現(xiàn)簡易計(jì)數(shù)器

    vuex實(shí)現(xiàn)簡易計(jì)數(shù)器

    這篇文章主要為大家詳細(xì)介紹了vuex實(shí)現(xiàn)一個(gè)簡易計(jì)數(shù)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • vue.js如何在網(wǎng)頁中實(shí)現(xiàn)一個(gè)金屬拋光質(zhì)感的按鈕

    vue.js如何在網(wǎng)頁中實(shí)現(xiàn)一個(gè)金屬拋光質(zhì)感的按鈕

    這篇文章主要給大家介紹了關(guān)于vue.js如何在網(wǎng)頁中實(shí)現(xiàn)一個(gè)金屬拋光質(zhì)感的按鈕的相關(guān)資料,文中給出了詳細(xì)的實(shí)例代碼以及圖文將實(shí)現(xiàn)的方法介紹的非常詳細(xì),需要的朋友可以參考下
    2023-04-04
  • vue中的attribute和property的具體使用及區(qū)別

    vue中的attribute和property的具體使用及區(qū)別

    本文主要介紹了vue中的attribute和property的具體使用及區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • vue中this.$refs的坑及解決

    vue中this.$refs的坑及解決

    這篇文章主要介紹了vue中this.$refs的坑及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 詳解如何使用vue實(shí)現(xiàn)頁面訪問攔截

    詳解如何使用vue實(shí)現(xiàn)頁面訪問攔截

    這篇文章主要為大家詳細(xì)介紹了如何使用vue實(shí)現(xiàn)頁面訪問攔截功能,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,需要的可以了解一下
    2023-08-08
  • 在antd Table中插入可編輯的單元格實(shí)例

    在antd Table中插入可編輯的單元格實(shí)例

    這篇文章主要介紹了在antd Table中插入可編輯的單元格實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • vue仿淘寶訂單狀態(tài)的tab切換效果

    vue仿淘寶訂單狀態(tài)的tab切換效果

    這篇文章主要為大家詳細(xì)介紹了vue仿淘寶訂單狀態(tài)tab切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Vite內(nèi)網(wǎng)ip訪問2種配置方式

    Vite內(nèi)網(wǎng)ip訪問2種配置方式

    這篇文章主要給大家介紹了關(guān)于Vite內(nèi)網(wǎng)ip訪問的2種配置方式,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Vite具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-07-07

最新評論

驻马店市| 西畴县| 涿州市| 齐河县| 墨玉县| 龙游县| 牡丹江市| 庐江县| 灵川县| 五常市| 敖汉旗| 扶风县| 丹寨县| 紫金县| 瓦房店市| 禄劝| 旬邑县| 舞阳县| 乌鲁木齐市| 工布江达县| 龙胜| 琼海市| 苏尼特左旗| 镇巴县| 防城港市| 吉林省| 信宜市| 罗城| 盱眙县| 建水县| 环江| 鸡西市| 原阳县| 观塘区| 芜湖市| 瑞安市| 新干县| 尉氏县| 钟祥市| 定日县| 宁城县|