响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。
响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。
名词解释:
- **副作用函数:**函数的执行会直接或间接影响其他函数的执行,这时我们说函数产生了副作用。副作用很容易产生,例如一个函数修改了全局变量,这其实也是一个副作用。
- targetmap 是一个 weakmap 类型的集合,用来存储副作用函数,从类型定义可以看出 targetmap的数据结构方式:
weakmap 由 target --> map 构成
map 由 key --> set 构成
- 其中 weakmap 的键是原始对象 target,weakmap 的值是一个 map 实例,map 的键是原始对象 target 的 key,map 的值是一个由副作用函数组成的 set。
- effect函数:创建一个副作用函数,接受两个参数,分别是用户自定义的fn函数和options 选项。
- track收集依赖:访问数据的时候,触发get函数,get函数最核心的部分是执行track函数收集依赖。这其实是一种懒操作。
- trigger派发更新:当对属性进行赋值时,会触发代理对象的 set 拦截函数执行。
track函数收集依赖的实现
export function track(target: object, type: trackoptypes, key: unknown) {
// 如果开启了依赖收集并且有正在执行的副作用,则收集依赖
if (shouldtrack && activeeffect) {
// 在 targetmap 中获取对应的 target 的依赖集合
let depsmap = targetmap.get(target)
if (!depsmap) {
// 如果 target 不在 targetmap 中,则将当前 target 添加进 targetmap 中,并将 targetmap 的 value 初始化为 new map()。
targetmap.set(target, (depsmap = new map()))
}
// 从依赖集合中获取对应的 key 的依赖
let dep = depsmap.get(key)
if (!dep) {
// 如果 key 不存在,将这个 key 作为依赖收集起来,并将依赖初始化为 new set()
depsmap.set(key, (dep = createdep()))
}
// 最后调用 trackeffects收集副作用函数,将副作用函数收集到依赖集合depsmap中。
const eventinfo = __dev__
? { effect: activeeffect, target, type, key }
: undefined
trackeffects(dep, eventinfo)
}
}
trackeffects 函数
收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
// 收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 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.
// 如果依赖中并不存当前的 effect 副作用函数
shouldtrack = !dep.has(activeeffect!)
}
if (shouldtrack) {
// 将当前的副作用函数收集进依赖中
dep.add(activeeffect!)
// 并在当前副作用函数的 deps 属性中记录该依赖
activeeffect!.deps.push(dep)
if (__dev__ && activeeffect!.ontrack) {
activeeffect!.ontrack(
object.assign(
{
effect: activeeffect!
},
debuggereventextrainfo
)
)
}
}
}
trigger 派发更新
对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:
const obj = { foo: 1 }
//通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行
const p = new proxy(obj, {
// 拦截设置操作
set(target, key, newval, receiver){
// 如果属性不存在,则说明是在添加新属性,否则设置已有属性
const type = object.prototype.hasownproperty.call(target,key) ? 'set' : 'add'
// 设置属性值
const res = reflect.set(target, key, newval, receiver)
// 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数
trigger(target,key,type)
return res
}
// 省略其他拦截函数
})
p.foo = 2
trigger 函数
根据target和key, 从targetMap中找到相关的所有副作用函数遍历执行一遍。
export function trigger(
target: object,
type: triggeroptypes,
key?: unknown,
newvalue?: unknown,
oldvalue?: unknown,
oldtarget?: map<unknown, unknown> | set<unknown>
) {
//首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。
const depsmap = targetmap.get(target)
// 该 target 从未被追踪,则不继续执行
if (!depsmap) {
// never been tracked
return
}
// 接着创建一个 set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。
let deps: (dep | undefined)[] = []
//接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。
//如果操作类型是 triggeroptypes.clear ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。
if (type === triggeroptypes.clear) {
// collection being cleared
// trigger all effects for target
// 当需要清除依赖时,将当前 target 的依赖全部传入
deps = [...depsmap.values()]
} else if (key === 'length' && isarray(target)) {
// 处理数组的特殊情况
depsmap.foreach((dep, key) => {
// 如果对应的长度, 有依赖收集需要更新
if (key === 'length' || key >= (newvalue as number)) {
deps.push(dep)
}
})
} else {
// schedule runs for set | add | delete
// 在 set | add | delete 的情况,添加当前 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 时触发map 数据结构的 keys 方法的副作用函数重新执行
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 时触发map 数据结构的 keys 方法的副作用函数重新执行
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[] = []
// 将需要执行的副作用函数收集到 effects 数组中
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__dev__) {
triggereffects(createdep(effects), eventinfo)
} else {
triggereffects(createdep(effects))
}
}
}
triggereffects 函数
triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
//triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
export function triggereffects(
dep: dep | reactiveeffect[],
debuggereventextrainfo?: debuggereventextrainfo
) {
// spread into array for stabilization
// 遍历需要执行的副作用函数集合
for (const effect of isarray(dep) ? dep : [...dep]) {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effect !== activeeffect || effect.allowrecurse) {
if (__dev__ && effect.ontrigger) {
effect.ontrigger(extend({ effect }, debuggereventextrainfo))
}
if (effect.scheduler) {
// 如果一个副作用函数存在调度器,则调用该调度器
effect.scheduler()
} else {
// 否则直接执行副作用函数
effect.run()
}
}
}
}
ES6 Proxy 方法
const p = new Proxy(person, { //创建代理
// 查
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发生了什么。
返回 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>
) {
/* 判断目标对象是否被effect */
/* observed 为经过 new Proxy代理的函数 */
let observed = toProxy.get(target) /* { [target] : obseved } */
if (observed !== void 0) { /* 如果目标对象已经被响应式处理,那么直接返回proxy的observed对象 */
return observed
}
if (toRaw.has(target)) { /* { [observed] : target } */
return target
}
/* 如果目标对象是 Set, Map, WeakMap, WeakSet 类型,那么 hander函数是 collectionHandlers 否侧目标函数是baseHandlers */
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
/* TODO: 创建响应式对象 */
observed = new Proxy(target, handlers)
/* target 和 observed 建立关联 */
toProxy.set(target, observed)
toRaw.set(observed, target)
/* 返回observed对象 */
return observed
}
Vue3响应式内部原理_vue3的响应式原理_monana6的博客-CSDN博客
vue3.0 响应式原理(超详细)_vue3响应式原理_我不是外星人Alien的博客-CSDN博客文章来源:https://www.toymoban.com/news/detail-686257.html
【干货】这次终于把 Vue3 响应式原理搞懂了!_傲娇的koala的博客-CSDN博客
ES6 之 Proxy 介绍_es6 proxy_barnett_y的博客-CSDN博客
vue3.0 响应式原理(超详细)_vue3响应式原理_我不是外星人Alien的博客-CSDN博客
文章来源地址https://www.toymoban.com/news/detail-686257.html
到了这里,关于Vue3响应式原理 私的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!