亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue3響應(yīng)式對(duì)象是如何實(shí)現(xiàn)的(2)

 更新時(shí)間:2022年08月30日 11:29:07   作者:???????咕咕雞_  
這篇文章主要介紹了Vue3響應(yīng)式對(duì)象是如何實(shí)現(xiàn)的,文章基于上篇文章展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下

前言

Vue3響應(yīng)式對(duì)象是如何實(shí)現(xiàn)的(1)中,我們已經(jīng)從功能上實(shí)現(xiàn)了一個(gè)響應(yīng)式對(duì)象。如果僅僅滿足于功能實(shí)現(xiàn),我們就可以止步于此了。但在上篇中,我們僅考慮了最簡(jiǎn)單的情況,想要完成一個(gè)完整可用的響應(yīng)式,需要我們繼續(xù)對(duì)細(xì)節(jié)深入思考。在特定場(chǎng)景下,是否存在BUG?是否還能繼續(xù)優(yōu)化?

分支切換的優(yōu)化

在上篇中,收集副作用函數(shù)是利用get自動(dòng)收集。那么被get自動(dòng)收集的副作用函數(shù),是否有可能會(huì)產(chǎn)生多余的觸發(fā)呢?或者說(shuō),我們其實(shí)進(jìn)行了多余的收集呢?同樣,還是從一個(gè)例子入手。

let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}

const objsMap = new WeakMap()
const data = { text: 'hello vue', ok: true } // (1)
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
  }
})
function track(target, key) {
  if(!activeEffect) return
  let propsMap = objsMap.get(target)
  if(!propsMap) {
    objsMap.set(target, (propsMap = new Map()))
  }
  let fns = propsMap.get(key)
  if(!fns) {
    propsMap.set(key, (fns = new Set()))
  }
  fns.add(activeEffect)
}

function trigger(target, key) {
  const propsMap = objsMap.get(target)
  if(!propsMap) return
  const fns = propsMap.get(key)
  fns && fns.forEach(fn => fn())
}

function fn() {
  document.body.innerText = obj.ok ? obj.text : 'ops...' // (2)
  console.log('Done!')
}
effect(fn)

這段代碼中,我們做了(1)(2)兩處更改。我們?cè)冢?)處給響應(yīng)式對(duì)象新增加了一個(gè)boolean類型的屬性ok,在(2)處我們利用ok的真值,來(lái)選擇將誰(shuí)賦值給document.body.innerText?,F(xiàn)在,我們將obj.ok的值置為false,這就意味著,document.body.innerText的值不再依賴于obj.text,而直接取字符串'ops...'

此時(shí),我們要能夠注意到一件事,雖然document.body.innerText的值不再依賴于obj.text了,但由于ok的初值是true,也就意味著在ok的值沒(méi)有改變時(shí),document.body.innerText的值依賴于obj.text,更進(jìn)一步說(shuō),這個(gè)函數(shù)已經(jīng)被obj.text當(dāng)作自己的副作用函數(shù)收集了。這會(huì)導(dǎo)致什么呢?

我們更改了obj.text的值,這會(huì)觸發(fā)副作用函數(shù)。但此時(shí)由于ok的值為false,界面上顯示的內(nèi)容沒(méi)有發(fā)生任何改變。也就是說(shuō),此時(shí)修改obj.text觸發(fā)的副作用函數(shù)的更新是不必要的。

這部分有些繞,讓我們通過(guò)畫(huà)圖來(lái)嘗試說(shuō)明。當(dāng)oktrue時(shí),數(shù)據(jù)結(jié)構(gòu)的狀態(tài)如圖所示:

從圖中可以看到,obj.textobj.ok都收集了同一個(gè)副作用函數(shù)fn。這也解釋了為什么即使我們將obj.ok的值為false,更改obj.text仍然會(huì)觸發(fā)副作用函數(shù)fn

我們希望的理想狀況是,當(dāng)okfalse時(shí),副作用函數(shù)fn被從obj.text的副作用函數(shù)收集器中刪除,數(shù)據(jù)結(jié)構(gòu)的狀態(tài)能改變?yōu)槿缦聽(tīng)顟B(tài)。

這就要求我們能夠在每次執(zhí)行副作用函數(shù)前,將該副作用函數(shù)從相關(guān)的副作用函數(shù)收集器中刪除,再重新建立聯(lián)系。為了實(shí)現(xiàn)這一點(diǎn),就要求我們記錄哪些副作用函數(shù)收集器收集了該副作用函數(shù)。

let activeEffect
function cleanup(effectFn) { // (3)
  for(let i = 0; i < effectFn.deps.length; i++) {
    const fns = effectFn.deps[i]
    fns.delete(effectFn)
  }
  effectFn.deps.length = 0
}
function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    fn()
  }
  effectFn.deps = [] // (1)
  effectFn()
}

const objsMap = new WeakMap()
const data = { text: 'hello vue', ok: true }
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
  }
})

function track(target, key) {
  if(!activeEffect) return
  let propsMap = objsMap.get(target)
  if(!propsMap) {
    objsMap.set(target, (propsMap = new Map()))
  }
  let fns = propsMap.get(key)
  if(!fns) {
    propsMap.set(key, (fns = new Set()))
  }
  fns.add(activeEffect)
  activeEffect.deps.push(fns) // (2)
}

function trigger(target, key) {
  const propsMap = objsMap.get(target)
  if(!propsMap) return
  const fns = propsMap.get(key)
  fns && fns.forEach(fn => fn())
}

function fn() {
  document.body.innerText = obj.ok ? obj.text : 'ops...'
  console.log('Done!')
}
effect(fn)

在這段代碼中,我們?cè)黾恿?處改動(dòng)。為了記錄副作用函數(shù)被哪些副作用函數(shù)收集器收集,我們?cè)冢?)處給每個(gè)副作用函數(shù)掛載了一個(gè)deps,用于記錄該副作用函數(shù)被誰(shuí)收集。在(2)處,副作用函數(shù)被收集時(shí),我們記錄副作用函數(shù)收集器。在(3)處,我們新增了cleanup函數(shù),從含有該副作用函數(shù)的副作用函數(shù)收集器中,刪除該副作用函數(shù)。

看上去好像沒(méi)啥問(wèn)題了,但是運(yùn)行代碼會(huì)發(fā)現(xiàn)產(chǎn)生了死循環(huán)。問(wèn)題出在哪呢?

以下面這段代碼為例:

const set = new Set([1])
set.forEach(item => {
    set.delete(1)
    set.add(1)
    console.log('Done!')
})

是的,這段代碼會(huì)產(chǎn)生死循環(huán)。原因是ECMAScript對(duì)Set.prototype.forEach的規(guī)范中明確,使用forEach遍歷Set時(shí),如果有值被直接添加到該Set上,則forEach會(huì)再次訪問(wèn)該值。

  const effectFn = () => {
    cleanup(effectFn) // (1)
    activeEffect = effectFn
    fn() // (2)
  }

同理,我們的代碼中,當(dāng)effectFn被執(zhí)行時(shí),(1)處的cleanup清除副作用函數(shù),就相當(dāng)于set.delete;而(2)處執(zhí)行副作用函數(shù)fn時(shí),會(huì)觸發(fā)依賴收集,將副作用函數(shù)又加入到了副作用函數(shù)收集器中,相當(dāng)于set.add,從而造成死循環(huán)。

解決的方法也很簡(jiǎn)單,我們只需要避免在原Set上直接進(jìn)行遍歷即可。

const set = new Set([1])
const otherSet = new Set(set)
otherSet.forEach(item => {
    set.delete(1)
    set.add(1)
    console.log('Done!')
})

在上例中,我們復(fù)制了setotherset中,otherset僅會(huì)執(zhí)行set.length次。按照這個(gè)思路,修改我們的代碼。

let activeEffect

function cleanup(effectFn) { 
  for(let i = 0; i < effectFn.deps.length; i++) {
    const fns = effectFn.deps[i]
    fns.delete(effectFn)
  }
  effectFn.deps.length = 0
}

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    fn()
  }
  effectFn.deps = [] 
  effectFn()
}

const objsMap = new WeakMap()
const data = { text: 'hello vue', ok: true }
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
  }
})

function track(target, key) {
  if(!activeEffect) return
  let propsMap = objsMap.get(target)
  if(!propsMap) {
    objsMap.set(target, (propsMap = new Map()))
  }
  let fns = propsMap.get(key)
  if(!fns) {
    propsMap.set(key, (fns = new Set()))
  }
  fns.add(activeEffect)
  activeEffect.deps.push(fns) 
}

function trigger(target, key) {
  const propsMap = objsMap.get(target)
  if(!propsMap) return
  const fns = propsMap.get(key)
  const otherFns = new Set(fns) // (1)
  otherFns.forEach(fn => fn())
}

function fn() {
  document.body.innerText = obj.ok ? obj.text : 'ops...'
  console.log('Done!')
}
effect(fn)

在(1)處我們新增了一個(gè)otherFns,復(fù)制了fns用來(lái)遍歷。讓我們?cè)賮?lái)看看結(jié)果。

①處,更改obj.ok的值為false,改變了頁(yè)面的顯示,沒(méi)有導(dǎo)致死循環(huán)。②處,當(dāng)obj.okfalse時(shí),副作用函數(shù)沒(méi)有執(zhí)行。至此,我們完成了針對(duì)分支切換場(chǎng)景下的優(yōu)化。

副作用函數(shù)嵌套產(chǎn)生的BUG

我們繼續(xù)從功能角度考慮,前面我們的副作用函數(shù)還是不夠復(fù)雜,實(shí)際應(yīng)用中(如組件嵌套渲染),副作用函數(shù)是可以發(fā)生嵌套的。

我們舉個(gè)簡(jiǎn)單的嵌套示例:

let t1, t2
effect(function effectFn1() {
  console.log('effectFn1')
  effect(function effectFn2() {
    console.log('effectFn2')
    t2 = obj.bar
  })
  t1 = obj.foo
})

這段代碼中,我們將effectFn2嵌入了effectFn1中,將obj.foo賦值給t1,obj.bar賦值給t2。從響應(yīng)式的功能上看,如果我們修改obj.foo的值,應(yīng)該會(huì)觸發(fā)effectFn1的執(zhí)行,且間接觸發(fā)effectFn2執(zhí)行。

修改obj.foo的值僅觸發(fā)了effectFn2的更新,這與我們的預(yù)期不符。既然是effect這里出了問(wèn)題,讓我們?cè)賮?lái)過(guò)一遍effect部分的代碼,看看能不能發(fā)現(xiàn)點(diǎn)什么。

let activeEffect // (1)

function cleanup(effectFn) { 
  for(let i = 0; i < effectFn.deps.length; i++) {
    const fns = effectFn.deps[i]
    fns.delete(effectFn)
  }
  effectFn.deps.length = 0
}

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    fn() // (2)
  }
  effectFn.deps = [] 
  effectFn()
}

仔細(xì)思考后,不難發(fā)現(xiàn)問(wèn)題所在。我們?cè)冢?)處定義了一個(gè)全局變量activeEffect用于副作用函數(shù)注冊(cè),這意味著同一時(shí)刻,我們僅能注冊(cè)一個(gè)副作用函數(shù)。在(2)處執(zhí)行了fn,此時(shí)注意,在我們給出的副作用函數(shù)嵌套示例中,effectFn1是先執(zhí)行effectFn2,再執(zhí)行t1 = obj.foo。也就是說(shuō),此時(shí)activeEffect注冊(cè)的副作用函數(shù)已經(jīng)由effectFn1變?yōu)榱?code>effectFn2。因此,當(dāng)執(zhí)行到t1 = obj.foo時(shí),track收集的activeEffect已經(jīng)是被effectFn2覆蓋過(guò)的。所以,修改obj.foo,trigger觸發(fā)的就是effectFn2了。

要解決這個(gè)問(wèn)題也很簡(jiǎn)單,既然后出現(xiàn)的要先被收集,后進(jìn)先出,用棧解決就好了。

let activeEffect
const effectStack = [] // (1)

function cleanup(effectFn) { 
  for(let i = 0; i < effectFn.deps.length; i++) {
    const fns = effectFn.deps[i]
    fns.delete(effectFn)
  }
  effectFn.deps.length = 0
}

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn 
    effectStack.push(effectFn)
    fn() // (2)
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  effectFn.deps = [] 
  effectFn()
}

這段代碼中,我們?cè)冢?)處定義了一個(gè)棧effectStack。不管(2)處如何更改activeEffect的內(nèi)容,都會(huì)被effectStack[effectStack.length - 1]回滾到原先正確的副作用函數(shù)上。

運(yùn)行的結(jié)果和我們的預(yù)期一致,到此為止,我們已經(jīng)完成了對(duì)嵌套副作用函數(shù)的處理。

自增/自減操作產(chǎn)生的BUG

這里還存在一個(gè)隱蔽的BUG,還和之前一樣,我們修改effect

effect(() => obj.foo++)

很簡(jiǎn)單的副作用函數(shù),這會(huì)有什么問(wèn)題呢?執(zhí)行一下看看。

很不幸,棧溢出了。這個(gè)副作用函數(shù)僅包含一個(gè)obj.foo++,所以可以確定,棧溢出就是由這個(gè)自增運(yùn)算引起的。接下來(lái)的問(wèn)題就是,這么簡(jiǎn)單的自增操作,怎么會(huì)引起棧溢出呢?為了更好的說(shuō)明問(wèn)題,讓我們先來(lái)拆解問(wèn)題。

effect(() => obj.foo = obj.foo + 1)

這段代碼中obj.foo = obj.foo + 1就等價(jià)于obj.foo++。這樣拆開(kāi)之后問(wèn)題一下就清楚了。這里同時(shí)進(jìn)行了obj.foogetset操作。先讀取obj.foo,收集了副作用函數(shù),再設(shè)置obj.foo,觸發(fā)了副作用函數(shù),而這個(gè)副作用函數(shù)中obj.foo又要被讀取,如此往復(fù),產(chǎn)生了死循環(huán)。為了驗(yàn)證這一點(diǎn),我們打印執(zhí)行的副作用函數(shù)。

上面的打印結(jié)果印證了我們的想法。造成這個(gè)BUG的主要原因是,當(dāng)getset操作同時(shí)存在時(shí),我們收集和觸發(fā)的都是同一個(gè)副作用函數(shù)。這里我們只需要添加一個(gè)守衛(wèi)條件:當(dāng)觸發(fā)的副作用函數(shù)正在被執(zhí)行時(shí),該副作用函數(shù)則不必再被執(zhí)行。

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) { // (1)
      otherFns.add(fn)
    }
  })
  otherFns.forEach(fn => fn())
}

如此一來(lái),相同的副作用函數(shù)僅會(huì)被觸發(fā)一次,避免了產(chǎn)生死循環(huán)。最后,我們驗(yàn)證一下即可。

到此這篇關(guān)于Vue3響應(yīng)式對(duì)象是如何實(shí)現(xiàn)的的文章就介紹到這了,更多相關(guān)Vue3響應(yīng)式對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決vue-router路由攔截造成死循環(huán)問(wèn)題

    解決vue-router路由攔截造成死循環(huán)問(wèn)題

    這篇文章主要介紹了解決vue-router路由攔截造成死循環(huán)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • vue項(xiàng)目tween方法實(shí)現(xiàn)返回頂部的示例代碼

    vue項(xiàng)目tween方法實(shí)現(xiàn)返回頂部的示例代碼

    這篇文章主要介紹了vue項(xiàng)目tween方法實(shí)現(xiàn)返回頂部,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • vue keep-alive列表頁(yè)緩存 詳情頁(yè)返回上一頁(yè)不刷新,定位到之前位置

    vue keep-alive列表頁(yè)緩存 詳情頁(yè)返回上一頁(yè)不刷新,定位到之前位置

    這篇文章主要介紹了vue keep-alive列表頁(yè)緩存 詳情頁(yè)返回上一頁(yè)不刷新,定位到之前位置,本文通過(guò)實(shí)例代碼效果圖展示給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2019-11-11
  • vue-router中的hash和history兩種模式的區(qū)別

    vue-router中的hash和history兩種模式的區(qū)別

    大家都知道vue-router有兩種模式,hash模式和history模式,這里來(lái)談?wù)剉ue-router中的hash和history兩種模式的區(qū)別。感興趣的朋友一起看看吧
    2018-07-07
  • vue實(shí)現(xiàn)標(biāo)簽頁(yè)切換/制作tab組件詳細(xì)教程

    vue實(shí)現(xiàn)標(biāo)簽頁(yè)切換/制作tab組件詳細(xì)教程

    在項(xiàng)目開(kāi)發(fā)中需要使用vue實(shí)現(xiàn)tab頁(yè)簽切換功能,所以這里總結(jié)下,這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)標(biāo)簽頁(yè)切換/制作tab組件的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • vue不通過(guò)路由直接獲取url中參數(shù)的方法示例

    vue不通過(guò)路由直接獲取url中參數(shù)的方法示例

    通過(guò)url傳遞參數(shù)是我們?cè)陂_(kāi)發(fā)中經(jīng)常用到的一種傳參方法,但通過(guò)url傳遞后改如果獲取呢?下面這篇文章主要給大家介紹了關(guān)于vue如何不通過(guò)路由直接獲取url中參數(shù)的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-08-08
  • vue+flv.js+SpringBoot+websocket實(shí)現(xiàn)視頻監(jiān)控與回放功能

    vue+flv.js+SpringBoot+websocket實(shí)現(xiàn)視頻監(jiān)控與回放功能

    vue+springboot的項(xiàng)目,需要在頁(yè)面展示出??档挠脖P(pán)錄像機(jī)連接的攝像頭的實(shí)時(shí)監(jiān)控畫(huà)面以及回放功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2022-02-02
  • Vue如何實(shí)現(xiàn)自動(dòng)觸發(fā)功能

    Vue如何實(shí)現(xiàn)自動(dòng)觸發(fā)功能

    這篇文章主要介紹了Vue如何實(shí)現(xiàn)自動(dòng)觸發(fā)功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Element中Select選擇器的實(shí)現(xiàn)

    Element中Select選擇器的實(shí)現(xiàn)

    本文主要介紹了Element中Select選擇器的實(shí)現(xiàn),文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • vue動(dòng)態(tài)合并單元格并添加小計(jì)合計(jì)功能示例

    vue動(dòng)態(tài)合并單元格并添加小計(jì)合計(jì)功能示例

    這篇文章主要給大家介紹了關(guān)于vue動(dòng)態(tài)合并單元格并添加小計(jì)合計(jì)功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評(píng)論