響應(yīng)式的本質(zhì):當(dāng)數(shù)據(jù)變化后會(huì)自動(dòng)執(zhí)行某個(gè)函數(shù)映射到組件,自動(dòng)觸發(fā)組件的重新渲染。
響應(yīng)式的實(shí)現(xiàn)方式就是劫持?jǐn)?shù)據(jù),Vue3的reactive就是通過Proxy劫持?jǐn)?shù)據(jù),由于劫持的是整個(gè)對(duì)象,所以可以檢測到任何對(duì)象的修改,彌補(bǔ)了2.0的不足。
名詞解釋:
- **副作用函數(shù):**函數(shù)的執(zhí)行會(huì)直接或間接影響其他函數(shù)的執(zhí)行,這時(shí)我們說函數(shù)產(chǎn)生了副作用。副作用很容易產(chǎn)生,例如一個(gè)函數(shù)修改了全局變量,這其實(shí)也是一個(gè)副作用。
- targetmap 是一個(gè) weakmap 類型的集合,用來存儲(chǔ)副作用函數(shù),從類型定義可以看出 targetmap的數(shù)據(jù)結(jié)構(gòu)方式:
weakmap 由 target --> map 構(gòu)成
map 由 key --> set 構(gòu)成
- 其中 weakmap 的鍵是原始對(duì)象 target,weakmap 的值是一個(gè) map 實(shí)例,map 的鍵是原始對(duì)象 target 的 key,map 的值是一個(gè)由副作用函數(shù)組成的 set。
- effect函數(shù):創(chuàng)建一個(gè)副作用函數(shù),接受兩個(gè)參數(shù),分別是用戶自定義的fn函數(shù)和options 選項(xiàng)。
- track收集依賴:訪問數(shù)據(jù)的時(shí)候,觸發(fā)get函數(shù),get函數(shù)最核心的部分是執(zhí)行track函數(shù)收集依賴。這其實(shí)是一種懶操作。
- trigger派發(fā)更新:當(dāng)對(duì)屬性進(jìn)行賦值時(shí),會(huì)觸發(fā)代理對(duì)象的 set 攔截函數(shù)執(zhí)行。
?
track函數(shù)收集依賴的實(shí)現(xiàn)
export function track(target: object, type: trackoptypes, key: unknown) {
// 如果開啟了依賴收集并且有正在執(zhí)行的副作用,則收集依賴
if (shouldtrack && activeeffect) {
// 在 targetmap 中獲取對(duì)應(yīng)的 target 的依賴集合
let depsmap = targetmap.get(target)
if (!depsmap) {
// 如果 target 不在 targetmap 中,則將當(dāng)前 target 添加進(jìn) targetmap 中,并將 targetmap 的 value 初始化為 new map()。
targetmap.set(target, (depsmap = new map()))
}
// 從依賴集合中獲取對(duì)應(yīng)的 key 的依賴
let dep = depsmap.get(key)
if (!dep) {
// 如果 key 不存在,將這個(gè) key 作為依賴收集起來,并將依賴初始化為 new set()
depsmap.set(key, (dep = createdep()))
}
// 最后調(diào)用 trackeffects收集副作用函數(shù),將副作用函數(shù)收集到依賴集合depsmap中。
const eventinfo = __dev__
? { effect: activeeffect, target, type, key }
: undefined
trackeffects(dep, eventinfo)
}
}
trackeffects 函數(shù)
收集副作用函數(shù),在 trackeffects 函數(shù)中,檢查當(dāng)前正在執(zhí)行的副作用函數(shù) activeeffect 是否已經(jīng)被收集到依賴集合中,如果沒有,就將當(dāng)前的副作用函數(shù)收集到依賴集合中。同時(shí)在當(dāng)前副作用函數(shù)的 deps 屬性中記錄該依賴。
// 收集副作用函數(shù),在 trackeffects 函數(shù)中,檢查當(dāng)前正在執(zhí)行的副作用函數(shù) activeeffect 是否已經(jīng)被收集到依賴集合中,如果沒有,就將當(dāng)前的副作用函數(shù)收集到依賴集合中。同時(shí)在當(dāng)前副作用函數(shù)的 deps 屬性中記錄該依賴。
export function trackeffects(
dep: dep,
debuggereventextrainfo?: debuggereventextrainfo
) {
let shouldtrack = false
if (effecttrackdepth <= maxmarkerbits) {
if (!newtracked(dep)) {
dep.n |= trackopbit // set newly tracked
shouldtrack = !wastracked(dep)
}
} else {
// full cleanup mode.
// 如果依賴中并不存當(dāng)前的 effect 副作用函數(shù)
shouldtrack = !dep.has(activeeffect!)
}
if (shouldtrack) {
// 將當(dāng)前的副作用函數(shù)收集進(jìn)依賴中
dep.add(activeeffect!)
// 并在當(dāng)前副作用函數(shù)的 deps 屬性中記錄該依賴
activeeffect!.deps.push(dep)
if (__dev__ && activeeffect!.ontrack) {
activeeffect!.ontrack(
object.assign(
{
effect: activeeffect!
},
debuggereventextrainfo
)
)
}
}
}
trigger 派發(fā)更新
對(duì)屬性進(jìn)行賦值時(shí),會(huì)觸發(fā)代理對(duì)象的 set 攔截函數(shù)執(zhí)行,如下面的代碼所示:
const obj = { foo: 1 }
//通過代理對(duì)象p 訪問 foo 屬性,便會(huì)觸發(fā) set 攔截函數(shù)的執(zhí)行
const p = new proxy(obj, {
// 攔截設(shè)置操作
set(target, key, newval, receiver){
// 如果屬性不存在,則說明是在添加新屬性,否則設(shè)置已有屬性
const type = object.prototype.hasownproperty.call(target,key) ? 'set' : 'add'
// 設(shè)置屬性值
const res = reflect.set(target, key, newval, receiver)
// 把副作用函數(shù)從桶里取出并執(zhí)行,將 type 作為第三個(gè)參數(shù)傳遞給 trigger 函數(shù)
trigger(target,key,type)
return res
}
// 省略其他攔截函數(shù)
})
p.foo = 2
trigger 函數(shù)
根據(jù)target和key, 從targetMap中找到相關(guān)的所有副作用函數(shù)遍歷執(zhí)行一遍。
export function trigger(
target: object,
type: triggeroptypes,
key?: unknown,
newvalue?: unknown,
oldvalue?: unknown,
oldtarget?: map<unknown, unknown> | set<unknown>
) {
//首先檢查當(dāng)前 target 是否有被追蹤,如果從未被追蹤過,即target的依賴未被收集,則不需要執(zhí)行派發(fā)更新,直接返回即可。
const depsmap = targetmap.get(target)
// 該 target 從未被追蹤,則不繼續(xù)執(zhí)行
if (!depsmap) {
// never been tracked
return
}
// 接著創(chuàng)建一個(gè) set 類型的 deps 集合,用來存儲(chǔ)當(dāng)前target的這個(gè) key 所有需要執(zhí)行派發(fā)更新的副作用函數(shù)。
let deps: (dep | undefined)[] = []
//接下來就根據(jù)操作類型type 和 key 來收集需要執(zhí)行派發(fā)更新的副作用函數(shù)。
//如果操作類型是 triggeroptypes.clear ,那么表示需要清除所有依賴,將當(dāng)前target的所有副作用函數(shù)添加到 deps 集合中。
if (type === triggeroptypes.clear) {
// collection being cleared
// trigger all effects for target
// 當(dāng)需要清除依賴時(shí),將當(dāng)前 target 的依賴全部傳入
deps = [...depsmap.values()]
} else if (key === 'length' && isarray(target)) {
// 處理數(shù)組的特殊情況
depsmap.foreach((dep, key) => {
// 如果對(duì)應(yīng)的長度, 有依賴收集需要更新
if (key === 'length' || key >= (newvalue as number)) {
deps.push(dep)
}
})
} else {
// schedule runs for set | add | delete
// 在 set | add | delete 的情況,添加當(dāng)前 key 的依賴
if (key !== void 0) {
deps.push(depsmap.get(key))
}
// also run for iteration key on add | delete | map.set
switch (type) {
case triggeroptypes.add:
if (!isarray(target)) {
deps.push(depsmap.get(iterate_key))
if (ismap(target)) {
// 操作類型為 add 時(shí)觸發(fā)map 數(shù)據(jù)結(jié)構(gòu)的 keys 方法的副作用函數(shù)重新執(zhí)行
deps.push(depsmap.get(map_key_iterate_key))
}
} else if (isintegerkey(key)) {
// new index added to array -> length changes
deps.push(depsmap.get('length'))
}
break
case triggeroptypes.delete:
if (!isarray(target)) {
deps.push(depsmap.get(iterate_key))
if (ismap(target)) {
// 操作類型為 delete 時(shí)觸發(fā)map 數(shù)據(jù)結(jié)構(gòu)的 keys 方法的副作用函數(shù)重新執(zhí)行
deps.push(depsmap.get(map_key_iterate_key))
}
}
break
case triggeroptypes.set:
if (ismap(target)) {
deps.push(depsmap.get(iterate_key))
}
break
}
}
const eventinfo = __dev__
? { target, type, key, newvalue, oldvalue, oldtarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__dev__) {
triggereffects(deps[0], eventinfo)
} else {
triggereffects(deps[0])
}
}
} else {
const effects: reactiveeffect[] = []
// 將需要執(zhí)行的副作用函數(shù)收集到 effects 數(shù)組中
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__dev__) {
triggereffects(createdep(effects), eventinfo)
} else {
triggereffects(createdep(effects))
}
}
}
triggereffects 函數(shù)
triggereffects 函數(shù)中,遍歷需要執(zhí)行的副作用函數(shù)集合,如果當(dāng)前副作用函數(shù)存在調(diào)度器,則執(zhí)行該調(diào)度器,否則直接執(zhí)行該副作用函數(shù)的 run 方法,執(zhí)行更新。
//triggereffects 函數(shù)中,遍歷需要執(zhí)行的副作用函數(shù)集合,如果當(dāng)前副作用函數(shù)存在調(diào)度器,則執(zhí)行該調(diào)度器,否則直接執(zhí)行該副作用函數(shù)的 run 方法,執(zhí)行更新。
export function triggereffects(
dep: dep | reactiveeffect[],
debuggereventextrainfo?: debuggereventextrainfo
) {
// spread into array for stabilization
// 遍歷需要執(zhí)行的副作用函數(shù)集合
for (const effect of isarray(dep) ? dep : [...dep]) {
// 如果 trigger 觸發(fā)執(zhí)行的副作用函數(shù)與當(dāng)前正在執(zhí)行的副作用函數(shù)相同,則不觸發(fā)執(zhí)行
if (effect !== activeeffect || effect.allowrecurse) {
if (__dev__ && effect.ontrigger) {
effect.ontrigger(extend({ effect }, debuggereventextrainfo))
}
if (effect.scheduler) {
// 如果一個(gè)副作用函數(shù)存在調(diào)度器,則調(diào)用該調(diào)度器
effect.scheduler()
} else {
// 否則直接執(zhí)行副作用函數(shù)
effect.run()
}
}
}
}
?ES6? Proxy 方法
const p = new Proxy(person, { //創(chuàng)建代理
// 查
get(target,propName){
console.log(`有人讀取了p身上的${propName}`)
return target[propName];
//反射
return Reflect.get(target,propName)
},
// 改 增
set(target, propName, value){
console.log(`有人修改了p身上的${propName}屬性`);
target[propName] = value;
},
// 刪
deleteProperty(target, propName){
console.log(`有人刪除了p身上的${propName}屬性`)
return delete target[propName];
},
});
createReactiveObject
之前說到的createReactiveObject,我們接下來看看createReactiveObject發(fā)生了什么。
返回 proxy
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
/* 判斷目標(biāo)對(duì)象是否被effect */
/* observed 為經(jīng)過 new Proxy代理的函數(shù) */
let observed = toProxy.get(target) /* { [target] : obseved } */
if (observed !== void 0) { /* 如果目標(biāo)對(duì)象已經(jīng)被響應(yīng)式處理,那么直接返回proxy的observed對(duì)象 */
return observed
}
if (toRaw.has(target)) { /* { [observed] : target } */
return target
}
/* 如果目標(biāo)對(duì)象是 Set, Map, WeakMap, WeakSet 類型,那么 hander函數(shù)是 collectionHandlers 否側(cè)目標(biāo)函數(shù)是baseHandlers */
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
/* TODO: 創(chuàng)建響應(yīng)式對(duì)象 */
observed = new Proxy(target, handlers)
/* target 和 observed 建立關(guān)聯(lián) */
toProxy.set(target, observed)
toRaw.set(observed, target)
/* 返回observed對(duì)象 */
return observed
}
Vue3響應(yīng)式內(nèi)部原理_vue3的響應(yīng)式原理_monana6的博客-CSDN博客
vue3.0 響應(yīng)式原理(超詳細(xì))_vue3響應(yīng)式原理_我不是外星人Alien的博客-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-686257.html
【干貨】這次終于把 Vue3 響應(yīng)式原理搞懂了!_傲嬌的koala的博客-CSDN博客
ES6 之 Proxy 介紹_es6 proxy_barnett_y的博客-CSDN博客
vue3.0 響應(yīng)式原理(超詳細(xì))_vue3響應(yīng)式原理_我不是外星人Alien的博客-CSDN博客
?文章來源地址http://www.zghlxwxcb.cn/news/detail-686257.html
到了這里,關(guān)于Vue3響應(yīng)式原理 私的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!