参考:
Vue3响应式原理juejin.cn/post/685666…
Vue3响应式原理剖析juejin.cn/post/686439…
github:github.com/vuejs/vue-n…
proxy能解决Object.defineProperty的以下问题:
- 要递归遍历对象,消耗大
- 数组响应化需要额外实现
- 新加或删除属性无法监听
- map,set无法响应式
建立响应式数据 */function reactice(obj){}/*
声明响应函数cb(依赖响应式数据) */function effect(cb){}/*
依赖收集:建立 数据&cb 映射关系 */function track(target,key){}/*
触发更新:根据映射关系,执行cb */function trigger(target,key){}
实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<button id="btn">click </button>
<script src="./xiao-reactivity.js"></script>
<script>
const root = document.getElementById('app')
const btn = document.getElementById('btn')
const o = {
name:'kkb',
age:1
}
let obj = reactive(o)
effect(()=>{
// 数据的修改,会通知这个函数执行
root.innerHTML = `
<h1>${obj.name}今年${obj.age}岁了</h1>
`
})
btn.addEventListener('click',()=>{
obj.age+=1
},false)
</script>
</body>
</html>
let targetMap = new WeakMap()
let effectStack = []//存储effect
function reactive(data){
return new Proxy(data,{
get(target,key){
track(target,key)
return target[key]
},
set(target,key,value){
target[key] = value
trigger(target,key)
}
})
}
//收集依赖
// targetMap: WeakMap{
// target:Map{
// key: Set[cb1,cb2...]
// }
// }
function track(target,key){
const effect = effectStack[effectStack.length-1]
if(effect){
let depsMap = targetMap.get(target)
if(!depsMap){
depsMap = new Map()
targetMap.set(target,depsMap)
}
let deps = depsMap.get(key)
if(!deps){
deps = new Set()
depsMap.set(key,deps)
}
if(!deps.has(effect)){
deps.add(effect)
}
}
}
//执行依赖
function trigger(target,key){
targetMap.get(target)?.get(key)?.forEach(effect=>effect())
}
//声明依赖
function effect(fn){
if(effectStack.indexOf(fn)===-1){//如果不加这个,有些effect语句访问target.name和target.age,队列会有两个相同的effect
try{
effectStack.push(fn)//程序执行到effect时推进任务栈
return fn() //执行effect,自然读取到代理对象,从而把这个effect存到相应key的依赖中,也可以不return,反正finally都会执行的,而且下面没其他代码了
}finally{
effectStack.pop() //清理任务栈
}
}
}