日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
手寫Vue3響應(yīng)式系統(tǒng):實(shí)現(xiàn)Computed

首先,我們簡單回顧一下:

創(chuàng)新互聯(lián)專注于企業(yè)全網(wǎng)營銷推廣、網(wǎng)站重做改版、紅旗網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5高端網(wǎng)站建設(shè)、成都做商城網(wǎng)站、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為紅旗等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

響應(yīng)式系統(tǒng)的核心就是一個(gè) WeakMap --- Map --- Set 的數(shù)據(jù)結(jié)構(gòu)。

WeakMap 的 key 是原對象,value 是響應(yīng)式的 Map。這樣當(dāng)對象銷毀的時(shí)候,對應(yīng)的 Map 也會銷毀。

Map 的 key 就是對象的每個(gè)屬性,value 是依賴這個(gè)對象屬性的 effect 函數(shù)的集合 Set。

然后用 Proxy 代理對象的 get 方法,收集依賴該對象屬性的 effect 函數(shù)到對應(yīng) key 的 Set 中。

還要代理對象的 set 方法,修改對象屬性的時(shí)候調(diào)用所有該 key 的 effect 函數(shù)。

上篇文章我們按照這樣的思路實(shí)現(xiàn)了一個(gè)比較完善的響應(yīng)式系統(tǒng),然后今天繼續(xù)實(shí)現(xiàn) computed。

實(shí)現(xiàn) computed

首先,我們把之前的代碼重構(gòu)一下,把依賴收集和觸發(fā)依賴函數(shù)的執(zhí)行抽離成 track 和 trigger 函數(shù):

邏輯還是添加 effect 到對應(yīng)的 Set,以及觸發(fā)對應(yīng) Set 里的 effect 函數(shù)執(zhí)行,但抽離出來清晰多了。

然后繼續(xù)實(shí)現(xiàn) computed。

computed 的使用大概是這樣的:

const value = computed(() => {
return obj.a + obj.b;
});

對比下 effect:

effect(() => {
console.log(obj.a);
});

區(qū)別只是多了個(gè)返回值。

所以我們基于 effect 實(shí)現(xiàn) computed 就是這樣的:

function computed(fn) {
const value = effect(fn);

return value
}

當(dāng)然,現(xiàn)在的 effect 是沒有返回值的,要給它加一下:

只是在之前執(zhí)行 effect 函數(shù)的基礎(chǔ)上把返回值記錄下來返回,這個(gè)改造還是很容易的。

現(xiàn)在 computed 就能返回計(jì)算后的值了:

但是現(xiàn)在數(shù)據(jù)一變,所有的 effect 都執(zhí)行了,而像 computed 這里的 effect 是沒必要每次都重新執(zhí)行的,只需要在數(shù)據(jù)變了之后執(zhí)行。

所以我們添加一個(gè) lazy 的 option 來控制 effect 不立刻執(zhí)行,而是把函數(shù)返回讓用戶自己執(zhí)行。

然后 computed 里用 effect 的時(shí)候就添加一個(gè) lazy 的 option,讓 effect 函數(shù)不執(zhí)行,而是返回出來。

computed 里創(chuàng)建一個(gè)對象,在 value 的 get 觸發(fā)時(shí)調(diào)用該函數(shù)拿到最新的值:

我們測試下:

可以看到現(xiàn)在 computed 返回值的 value 屬性是能拿到計(jì)算后的值的,并且修改了 obj.a. 之后會重新執(zhí)行計(jì)算函數(shù),再次拿 value 時(shí)能拿到新的值。

只是多執(zhí)行了一次計(jì)算,這是因?yàn)?obj.a 變的時(shí)候會執(zhí)行所有的 effect 函數(shù):

這樣每次數(shù)據(jù)變了都會重新執(zhí)行 computed 的函數(shù)來計(jì)算最新的值。

這是沒有必要的,effect 的函數(shù)是否執(zhí)行應(yīng)該也是可以控制的。所以我們要給它加上調(diào)度的功能:

可以支持傳入 schduler 回調(diào)函數(shù),然后執(zhí)行 effect 的時(shí)候,如果有 scheduler 就傳給它讓用戶自己來調(diào)度,否則才執(zhí)行 effect 函數(shù)。

這樣用戶就可以自己控制 effect 函數(shù)的執(zhí)行了:

然后再試一下剛才的代碼:

可以看到,obj.a 變了之后并沒有執(zhí)行 effect 函數(shù)來重新計(jì)算,因?yàn)槲覀兗恿?sheduler 來自己調(diào)度。這樣就避免了數(shù)據(jù)變了以后馬上執(zhí)行 computed 函數(shù),可以自己控制執(zhí)行。

現(xiàn)在還有一個(gè)問題,每次訪問 res.value 都要計(jì)算:

能不能加個(gè)緩存呢?只有數(shù)據(jù)變了才需要計(jì)算,否則直接拿之前計(jì)算的值。

當(dāng)然是可以的,加個(gè)標(biāo)記就行:

scheduler 被調(diào)用的時(shí)候就說明數(shù)據(jù)變了,這時(shí)候 dirty 設(shè)置為 true,然后取 value 的時(shí)候就重新計(jì)算,之后再改為 false,下次取 value 就直接拿計(jì)算好的值了。

我們測試下:

我們訪問 computed 值的 value 屬性時(shí),第一次會重新計(jì)算,后面就直接拿計(jì)算好的值了。

修改它依賴的數(shù)據(jù)后,再次訪問 value 屬性會再次重新計(jì)算,然后后面再訪問就又會直接拿計(jì)算好的值了。

至此,我們完成了 computed 的功能。

但現(xiàn)在的 computed 實(shí)現(xiàn)還有一個(gè)問題,比如這樣一段代碼:

let res = computed(() => {
return obj.a + obj.b;
});

effect(() => {
console.log(res.value);
});

我們在一個(gè) effect 函數(shù)里用到了 computed 值,按理說 obj.a 變了,那 computed 的值也會變,應(yīng)該觸發(fā)所有的 effect 函數(shù)。

但實(shí)際上并沒有:

這是為什么呢?

這是因?yàn)榉祷氐?computed 值并不是一個(gè)響應(yīng)式的對象,需要把它變?yōu)轫憫?yīng)式的,也就是 get 的時(shí)候 track 收集依賴,set 的時(shí)候觸發(fā)依賴的執(zhí)行:

我們再試一下:

現(xiàn)在 computed 值變了就能觸發(fā)依賴它的 effect 了。

至此,我們的 computed 就很完善了。

完整代碼如下:

const data = {
a: 1,
b: 2
}

let activeEffect
const effectStack = [];

function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn)

activeEffect = effectFn
effectStack.push(effectFn);

const res = fn()

effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]

return res
}
effectFn.deps = []
effectFn.options = options;

if (!options.lazy) {
effectFn()
}

return effectFn
}

function computed(fn) {
let value
let dirty = true
const effectFn = effect(fn, {
lazy: true,
scheduler(fn) {
if(!dirty) {
dirty = true
trigger(obj, 'value');
}
}
});

const obj = {
get value() {
if (dirty) {
value = effectFn()
dirty = false
}
track(obj, 'value');
console.log(obj);
return value
}
}

return obj
}

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

const reactiveMap = new WeakMap()

const obj = new Proxy(data, {
get(targetObj, key) {
track(targetObj, key);

return targetObj[key]
},
set(targetObj, key, newVal) {
targetObj[key] = newVal

trigger(targetObj, key)
}
})

function track(targetObj, key) {
let depsMap = reactiveMap.get(targetObj)

if (!depsMap) {
reactiveMap.set(targetObj, (depsMap = new Map()))
}
let deps = depsMap.get(key)

if (!deps) {
depsMap.set(key, (deps = new Set()))
}

deps.add(activeEffect)

activeEffect.deps.push(deps);
}

function trigger(targetObj, key) {
const depsMap = reactiveMap.get(targetObj)

if (!depsMap) return

const effects = depsMap.get(key)

const effectsToRun = new Set(effects)
effectsToRun.forEach(effectFn => {
if(effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}

總結(jié)

上篇文章我們實(shí)現(xiàn)了響應(yīng)式的核心數(shù)據(jù)結(jié)構(gòu),依賴的收集、數(shù)據(jù)變化后通知依賴函數(shù)執(zhí)行。今天我們在那基礎(chǔ)上實(shí)現(xiàn)了 computed。

我們改造了 effect 函數(shù),讓它返回傳入的 fn,然后在 computed 里自己執(zhí)行來拿到計(jì)算后的值。

我們又支持了 lazy 和 scheduler 的 option,lazy 是讓 effect 不立刻執(zhí)行傳入的函數(shù),scheduler 是在數(shù)據(jù)變動(dòng)觸發(fā)依賴執(zhí)行的時(shí)候回調(diào) sheduler 來調(diào)度。

我們通過標(biāo)記是否 dirty 來實(shí)現(xiàn)緩存,當(dāng) sheduler 執(zhí)行的時(shí)候,說明數(shù)據(jù)變了,把 dirty 置為 true,重新計(jì)算 computed 的值,否則直接拿緩存。

此外,computed 的 value 并不是響應(yīng)式對象,我們需要單獨(dú)的調(diào)用下 track 和 trigger。

這樣,我們就實(shí)現(xiàn)了完善的 computed 功能,vue3 內(nèi)部也是這樣實(shí)現(xiàn)的。


本文標(biāo)題:手寫Vue3響應(yīng)式系統(tǒng):實(shí)現(xiàn)Computed
本文路徑:http://www.5511xx.com/article/dpiehod.html