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

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

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

簡(jiǎn)單的響應(yīng)式實(shí)現(xiàn)

為了方便說(shuō)明,先來(lái)看一個(gè)簡(jiǎn)單的例子。

const obj = { text: 'hello vue' }
function effect() {
  document.body.innerText = obj.text
}

這段代碼中,如果obj是一個(gè)響應(yīng)式數(shù)據(jù),會(huì)產(chǎn)生什么效果呢?當(dāng)obj.text中的內(nèi)容改變時(shí),document.body.innerText也會(huì)隨之改變,從而修改頁(yè)面上顯示的內(nèi)容。因此,如果僅從這個(gè)簡(jiǎn)單的例子出發(fā),在修改obj后,再次執(zhí)行effect(),就能夠?qū)崿F(xiàn)響應(yīng)式。此時(shí),effect()被稱(chēng)為副作用函數(shù),其副作用體現(xiàn)在document.body.innerText這個(gè)非effect()函數(shù)作用域內(nèi)的值被修改了。

實(shí)際使用中,響應(yīng)式實(shí)現(xiàn)要復(fù)雜的多。為了進(jìn)一步實(shí)現(xiàn)響應(yīng)式,讓我們基于上文的例子,再來(lái)捋一捋響應(yīng)式實(shí)現(xiàn)需要什么。我們需要在修改響應(yīng)式數(shù)據(jù)后觸發(fā)副作用函數(shù)。仔細(xì)思考這句話,這里其實(shí)要求了兩個(gè)功能:

  • 當(dāng)obj讀取時(shí),收集副作用函數(shù);
  • 當(dāng)obj修改時(shí),觸發(fā)收集的副作用函數(shù);

問(wèn)題更清晰了,我們只需要攔截讀取和修改操作分別進(jìn)行收集和觸發(fā),就能夠?qū)崿F(xiàn)響應(yīng)式了。這里我們可以使用Proxy

Proxy與響應(yīng)式

為什么需要Proxy?

Vue3的核心特征之一就是響應(yīng)式,而實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式依賴(lài)于底層的Proxy。因此,想要完成Vue的響應(yīng)式功能,首先需要理解Proxy。

reactive為例,當(dāng)想要?jiǎng)?chuàng)建一個(gè)響應(yīng)式對(duì)象時(shí),僅需要使用reactive對(duì)原始對(duì)象進(jìn)行包裹。

例如:

const user = reactive({
  age: 25
})

在Vue3的官方文檔中,對(duì)reactive的描述是:返回對(duì)象的響應(yīng)式副本。既然是響應(yīng)式副本,就需要產(chǎn)生一個(gè)與原始對(duì)象含有相同內(nèi)容的響應(yīng)式對(duì)象,之后使用這個(gè)響應(yīng)式對(duì)象替代原始對(duì)象,代替原始對(duì)象去做事情。這就體現(xiàn)了Proxy的價(jià)值——創(chuàng)建原始對(duì)象的代理。

Proxy創(chuàng)建的代理對(duì)象與原始對(duì)象有何不同?

先來(lái)看看如何使用Proxy創(chuàng)建代理對(duì)象。

let proxy = new Proxy(target, handler)

Proxy可以接收兩個(gè)參數(shù),其中,target表示被代理的原始對(duì)象,handler表示代理配置,代理配置中的捕捉器允許我們攔截重新定義針對(duì)代理對(duì)象的操作。因此,如果在Proxy中,不進(jìn)行任何代理配置,Proxy僅會(huì)成為原始對(duì)象的一個(gè)透明包裝器(僅創(chuàng)建原始對(duì)象副本,但不產(chǎn)生任何其它功能)。因此,為了利用Proxy實(shí)現(xiàn)響應(yīng)式對(duì)象,我們必須使用Proxy中的代理配置。

在JavaScript規(guī)范中,存在一個(gè)描述JavaScript運(yùn)行機(jī)制的“內(nèi)部方法”。其中,讀取屬性的操作被稱(chēng)為[[Get]],設(shè)置屬性的操作被稱(chēng)為[[Set]]。通常,我們無(wú)法直接使用這些“內(nèi)部方法”,但Proxy給我們提供了捕捉器,使得對(duì)讀取和設(shè)置操作的攔截成為可能。

const p = new Proxy(obj, {
  get(target, property, receiver) {/*攔截讀取屬性操作*/}
  set(target, property, value, receiver) {/*攔截設(shè)置屬性操作*/}
})

其中,target為被代理的原始對(duì)象,property為原始對(duì)象的屬性名,value為設(shè)置目標(biāo)屬性的值receiver是屬性所在的this對(duì)象,即Proxy代理對(duì)象。

Proxy是由JavaSciprt引擎實(shí)現(xiàn)的ES6特性,因此,沒(méi)有ES5的polyfill,也無(wú)法使用Babel轉(zhuǎn)譯。這也是Vue3不支持低版本IE的主要原因。

let fn
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    fn = effect
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fn()
    return true
  }
})

多副作用函數(shù)的響應(yīng)式實(shí)現(xiàn)

如果響應(yīng)式數(shù)據(jù)有多個(gè)相關(guān)的副作用函數(shù)應(yīng)該如何處理。

在上例中,存儲(chǔ)一個(gè)副作用函數(shù)我們可以使用一個(gè)fn進(jìn)行緩存,多個(gè)副作用函數(shù),我們?cè)谑占瘯r(shí)用數(shù)組把它裝起來(lái)就好,需要觸發(fā)時(shí),再遍歷數(shù)組挨個(gè)觸發(fā)收集到的副作用函數(shù)。

const fns = []
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    fns.push(effect)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fns.forEach(fn => fn())
    return true
  }
})

這樣可以嗎?可以是可以,功能上已經(jīng)實(shí)現(xiàn)了。接下來(lái)讓我們來(lái)看看有沒(méi)有哪些地方可以?xún)?yōu)化。

第一個(gè)可以?xún)?yōu)化的點(diǎn)是,需要考慮被重復(fù)收集的函數(shù)。

舉個(gè)例子:

const effect1 = () => {
  document.body.innerText = obj.text
}
const effect2 = effect1

在這個(gè)例子中,effect1effect2都表示同一個(gè)函數(shù),但在收集時(shí)會(huì)被重復(fù)收集,執(zhí)行時(shí)也會(huì)被重復(fù)執(zhí)行。這些重復(fù)顯然是不必要的,因此,我們需要在收集副作用函數(shù)的時(shí)候,干掉重復(fù)的函數(shù)。去重可以考慮Set。

const fns = new Set()
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    fns.push(effect)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fns.forEach(fn => fn())
    return true
  }
})

不知道大家是否注意到,為了演示方便,我們?cè)谑占貢r(shí),都默認(rèn)被收集的副作用函數(shù)名是effect。但實(shí)際開(kāi)發(fā)中,函數(shù)名肯定不會(huì)是固定的或是有規(guī)律可循的,我們肯定不能按函數(shù)名一個(gè)個(gè)手動(dòng)收集,因此,我們要想一種方式能夠不依賴(lài)于函數(shù)名進(jìn)行副作用函數(shù)收集。為了實(shí)現(xiàn)這一點(diǎn),我們將副作用函數(shù)進(jìn)行包裹,用一個(gè)activeEffect統(tǒng)一注冊(cè)。

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

const fns = new Set()
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    if(activeEffect) {
      fns.add(activeEffect)
    }
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fns.forEach(fn => fn())
    return true
  }
})

function fn() {
  document.body.innerText = obj.text
}
effect(fn)

到這里,我們已經(jīng)把容易想到的優(yōu)化都處理了。但這還沒(méi)完,這里還有個(gè)隱蔽的點(diǎn)沒(méi)有考慮到。此時(shí),我們的響應(yīng)式粒度還不夠細(xì),副作用函數(shù)的收集和觸發(fā)的最小單位是響應(yīng)式對(duì)象,這會(huì)導(dǎo)致不必要的副作用函數(shù)更新。例如,我們給出一個(gè)obj上不存在的屬性obj.notExist,對(duì)其進(jìn)行賦值操作。為了方便演示,我們?cè)?code>fn()中加一條打印語(yǔ)句,觀察結(jié)果。

function fn() {
  document.body.innerText = obj.text
  console.log('Done!')
}

可以看到,副作用函數(shù)被觸發(fā)了。這里obj.notExist屬性在obj上是不存在的,更談不上對(duì)應(yīng)了哪個(gè)副作用函數(shù),因此,這里的副作用函數(shù)不應(yīng)該被觸發(fā),或者換句話說(shuō),副作用函數(shù)僅能被對(duì)應(yīng)的響應(yīng)式對(duì)象的屬性影響,且僅在該屬性被修改時(shí)觸發(fā)。

更細(xì)粒度的觸發(fā)條件就要求我們收集副作用函數(shù)時(shí),針對(duì)響應(yīng)式對(duì)象的屬性進(jìn)行收集。聽(tīng)上去有點(diǎn)無(wú)從下手,讓我們?cè)賮?lái)捋一捋。既然是響應(yīng)式對(duì)象,首先還是要保證利用響應(yīng)式對(duì)象的Proxy來(lái)進(jìn)行收集和觸發(fā),但在收集的時(shí)候,就不能一股腦的把所有屬性對(duì)應(yīng)的副作用函數(shù)塞到一塊了,需要把誰(shuí)是誰(shuí)的分清楚(即一個(gè)屬性對(duì)應(yīng)了哪些副作用函數(shù)),讓每個(gè)屬性擁有自己收集和觸發(fā)副作用函數(shù)的能力,具體對(duì)應(yīng)關(guān)系如圖所示。

讓我們從后往前看。首先,屬性的副作用函數(shù)收集器我們可以沿用上文的思路,使用Set實(shí)現(xiàn)。再往前,每個(gè)屬性都需要配備一個(gè)副作用函數(shù)收集器,這是一個(gè)一對(duì)一的關(guān)系,我們可以使用將屬性值作為key,副作用收集器作為value,使用Map實(shí)現(xiàn),而這個(gè)Map就包含了單個(gè)響應(yīng)式對(duì)象的全部?jī)?nèi)容。

回看我們上面的代碼,其實(shí)我們?cè)谶M(jìn)行副作用函數(shù)收集的時(shí)候,僅使用了一個(gè)全局的容器承載。在實(shí)際開(kāi)發(fā)中,我們需要盡量避免大量出現(xiàn)全局定義。因此,我們將響應(yīng)式對(duì)象放進(jìn)一個(gè)全局容器中統(tǒng)一管理。我們需要將每一個(gè)對(duì)象和其對(duì)應(yīng)的Map建立一對(duì)一的映射關(guān)系。整體關(guān)系如下圖所示:

 按圖中思路,我們?cè)诖a中重新組織數(shù)據(jù)結(jié)構(gòu)。

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

const objsMap = new Map() // 全局容器
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    if(!activeEffect) return
    let propsMap = objsMap.get(target) // 屬性容器
    if(!propsMap) {
      objsMap.set(target, (propsMap = new Map()))
    }
    let fns = propsMap.get(key) // 副作用函數(shù)收集器
    if(!fns) {
      propsMap.set(key, (fns = new Set()))
    }
    fns.add(activeEffect)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    const propsMap = objsMap.get(target) // 屬性容器
    if(!propsMap) return
    const fns = propsMap.get(key) // 副作用函數(shù)收集器
    fns && fns.forEach(fn => fn())
    return true
  }
})

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

執(zhí)行我們更新的代碼,我們就能避免無(wú)效更新,只觸發(fā)與屬性相關(guān)的更新了。

到這結(jié)束了嗎?我們其實(shí)還能再做一點(diǎn)優(yōu)化。如果一個(gè)響應(yīng)式對(duì)象沒(méi)有被引用時(shí),說(shuō)明這個(gè)對(duì)象不被使用,不被使用的對(duì)象應(yīng)該被JavaScript的垃圾回收器回收,避免造成內(nèi)存占用。但Map的特性(Map會(huì)被其key值持續(xù)引用)決定了,即使沒(méi)有任何引用,Map中的對(duì)象也不能被垃圾回收器回收。解決這個(gè)問(wèn)題,只需要用WeakMap來(lái)代替Map,WeakMap的key是弱引用。到這里我們終于大功告成了,把功能實(shí)現(xiàn)完了。

功能實(shí)現(xiàn)完接下來(lái)要干嘛?對(duì),要重構(gòu),要看有沒(méi)有能重構(gòu)的地方。getset中的內(nèi)容有點(diǎn)臃腫了,想想一開(kāi)始說(shuō)的,get時(shí)收集,set時(shí)觸發(fā)。最后,就讓我們來(lái)抽離封裝這兩部分。

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

const objsMap = new WeakMap()
const data = { text: 'hello vue' }
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)
}
// 觸發(fā)
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.text
  console.log('Done!')
}
effect(fn)

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

相關(guān)文章

  • vue文本識(shí)別"\n"換行問(wèn)題的解決方式

    vue文本識(shí)別"\n"換行問(wèn)題的解決方式

    在頁(yè)面中經(jīng)常會(huì)遇到自定義文本,如果文本過(guò)長(zhǎng)就需要換行,在HTML中可以通過(guò)標(biāo)簽換行,也可以通過(guò)\n轉(zhuǎn)椅字符換行,下面這篇文章主要給大家介紹了關(guān)于vue文本識(shí)別“\n”換行問(wèn)題的解決方式,需要的朋友可以參考下
    2022-11-11
  • vue項(xiàng)目實(shí)現(xiàn)多語(yǔ)言切換的思路

    vue項(xiàng)目實(shí)現(xiàn)多語(yǔ)言切換的思路

    這篇文章主要介紹了vue項(xiàng)目實(shí)現(xiàn)多語(yǔ)言切換的思路,幫助大家完成多語(yǔ)言翻譯,感興趣的朋友可以了解下
    2020-09-09
  • Vue v-model實(shí)現(xiàn)案例介紹

    Vue v-model實(shí)現(xiàn)案例介紹

    v-model就是vue的雙向綁定的指令,能將頁(yè)面上控件輸入的值同步更新到相關(guān)綁定的data屬性,也會(huì)在更新data綁定屬性時(shí)候,更新頁(yè)面上輸入控件的值
    2022-09-09
  • vue實(shí)現(xiàn)手機(jī)端省市區(qū)區(qū)域選擇

    vue實(shí)現(xiàn)手機(jī)端省市區(qū)區(qū)域選擇

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)手機(jī)端省市區(qū)區(qū)域選擇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • VUE中使用滾動(dòng)組件-vueSeamlessScroll

    VUE中使用滾動(dòng)組件-vueSeamlessScroll

    這篇文章主要介紹了VUE中使用滾動(dòng)組件-vueSeamlessScroll,需要的朋友可以參考下
    2023-10-10
  • vue?下載文檔亂碼的解決

    vue?下載文檔亂碼的解決

    這篇文章主要介紹了vue?下載文檔亂碼的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • VSCode搭建Vue項(xiàng)目的方法

    VSCode搭建Vue項(xiàng)目的方法

    這篇文章主要介紹了VSCode搭建Vue項(xiàng)目的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 詳解Vue如何將多個(gè)空格被合并顯示成一個(gè)空格

    詳解Vue如何將多個(gè)空格被合并顯示成一個(gè)空格

    這篇文章主要為大家詳細(xì)介紹了在Vue中如何將多個(gè)空格被合并顯示成一個(gè)空格,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • vite打包只生成了一個(gè)css和js文件問(wèn)題的解決方法

    vite打包只生成了一個(gè)css和js文件問(wèn)題的解決方法

    今天整了一個(gè)項(xiàng)目,試了下打包,發(fā)下打包后只生成了一個(gè)css文件,和一個(gè)js文件,這樣肯定是不行的,因?yàn)檫@樣這個(gè)文件的包大小很大,第一次訪問(wèn)會(huì)導(dǎo)致白屏問(wèn)題,所以本文給大家介紹了vite打包只生成了一個(gè)css和js文件問(wèn)題的解決方法,需要的朋友可以參考下
    2024-05-05
  • 簡(jiǎn)單了解vue中的v-if和v-show的區(qū)別

    簡(jiǎn)單了解vue中的v-if和v-show的區(qū)別

    這篇文章主要介紹了簡(jiǎn)單了解vue中的v-if和v-show的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10

最新評(píng)論

吉木萨尔县| 望都县| 新乡县| 苏尼特右旗| 铜梁县| 富宁县| 屏山县| 蕉岭县| 蒙阴县| 蓬安县| 阿克陶县| 许昌县| 岐山县| 淮滨县| 县级市| 平谷区| 古蔺县| 延寿县| 琼结县| 巴彦县| 龙里县| 辽宁省| 横山县| 宜城市| 咸丰县| 灵川县| 峡江县| 方正县| 洛隆县| 廊坊市| 夏津县| 东海县| 宁城县| 宣城市| 璧山县| 玛曲县| 林州市| 玉田县| 安达市| 上高县| 南岸区|