是什么
副作用函数
function effect(){
document.body.innerText = obj.text
}
函数中使用设置了document.body.innerText的值,而全局其他地方也能设置这个值,因此这个函数的执行会间接影响其他地方,便产生了副作用
响应式数据
当数据改变时,其它使用这个数据的函数自动执行,
则称这个数据是响应式数据
实现
- 当副作用函数effect执行,使用读取响应式数据,也就是触发数据的
get
操作--------进行effect 函数的收集 - 当响应式数据改变,也就是触发数据的
set
操作—取出effect数据重新执行
需要截取数据对象的get与set操作
vue2中使用Object.defineProperty()
实现
vue3中使用Proxy
实现
需求1: effct函数与响应式数据绑定
//副作用函数
function effect(){
document.body.innerText = obj.test
}
//存储副作用函数的桶
const bucket = new Set()
//原始数据
const data = {test:"hello world"}
//对原始数据的代理
const obj = new Proxy(data,{
//拦截读取操作
get(target,key){
//将副作用函数添加到桶中
bucket.add(effect)
//返回属性值
return target[key]
},
//拦截写入操作
set(target,key,newValue){
//设置属性值
target[key] = newValue
//把副作用函数从桶里取出执行
bucket.forEach(fn => fn())
return true;//返回true代表设置成功
}
})
// 1. 触发副作用函数,触发读取
effect()
//2.修改数据,触发set
//1秒后修改数据
setTimeout(() => {
obj.test = "hello vue3"
},1000)
需求2:副作用函数的依赖收集
未实现:
- 副作用函数的依赖收集(如果副作用函数是匿名函数,或者不叫effect,那么桶中就不会有这个函数)
//用一个全局变量来存储被注册的副作用函数,
let activeEffect = null
//存储副作用函数的桶
const bucket = new Set()
//effect 函数用来注册副作用函数
function effect(fn){
//将副作用函数赋值给activeEffect
activeEffect = fn
//执行副作用函数
fn()
}
//原始数据
const data = {test:"hello world"}
//对原始数据的代理
const obj = new Proxy(data,{
get(target,key){
//只有当activeEffect有值的时候,才将其添加到桶中
if(activeEffect){
//将activeEffect添加到桶中
bucket.add(activeEffect)
}
return target[key]
},
set(target,key,newValue){
target[key] = newValue
//把副作用函数从桶里取出执行
bucket.forEach(fn => fn())
return true;//返回true代表设置成功
}
})
//1.副作用函数执行 , 触发get
effect(function(){
document.body.innerText = obj.test;
})
//2.修改数据 , 触发set
setTimeout(() => {
obj.test = "hello vue3"
},1000)
需求3:字段与函数建立明确的联系
发现bug:
当给代理对象添加不存在的属性的时候,按理说不应该触发副作用函数,但是实际上触发了!!!
导致该问题的根本原因是,我们没有在副作用函数与被操作的目标字段之间建立明确的联系。
- 当设置属性时,无论设置的是哪一个属性,也都会把“桶”里的副作用函数取出并执行
- 当读取属性时,无论读取的是哪一个属性,都会把副作用函数收集到“桶”里;
解决方案:
-
在副作用函数与被操作的目标字段之间建立明确的联系
-
重新设计桶结构:树形结构
不难看出是树形结构
选择weakMap代替set
weakMap表示键值对对象数据结构,键只能是对象,是弱引用
,当在WeakMap中使用的是某个对象的引用作为键,那么这个对象被回收后(即没有其他地方引用它),对应的键值对也会被自动删除。这种弱引用特性有助于避免内存泄漏。
//存储副作用函数的桶
const bucket = new WeakMap()
// 全局变量,存储被注册的副作用函数
let activeEffect = null
//effect 函数用来注册副作用函数
function effect(fn){
//将副作用函数赋值给activeEffect
activeEffect = fn
//执行副作用函数
fn()
}
//将副作用函数添加到桶中
function track(target,key)
{
//根据target从桶中获取 depsMap,它也是一个Map类型:key是目标对象,value还是个Map,存储着副作用函数
let depsMap = bucket.get(target)
if(!depsMap){
depsMap = new Map()
bucket.set(target,depsMap)
}
//根据key从depsMap中获取 deps,它是一个Set类型,存储着与当前key相关联的副作用函数
let deps = depsMap.get(key)
if(!deps){
deps = new Set()
depsMap.set(key,deps)
}
//将当前副作用函数添加到deps中
deps.add(activeEffect)
}
//从桶中根据target和key获取对应的副作用函数
function trigger(target,key)
{
//根据target从桶中获取 depsMap,它也是一个Map类型:key是目标对象,value还是个Map,存储着副作用函数
let depsMap = bucket.get(target)
if(!depsMap){
depsMap = new Map()
bucket.set(target,depsMap)
}
//根据key从depsMap中获取 deps,它是一个Set类型,存储着与当前key相关联的副作用函数
let deps = depsMap.get(key)
if(!deps){
deps = new Set()
depsMap.set(key,deps)
}
//把副作用函数从桶里取出执行
deps.forEach(fn => fn())
}
//原始数据
const data = {test:"hello world"}
//对原始数据的代理
const obj = new Proxy(data,{
get(target,key){
//只有当activeEffect有值的时候,才将其添加到桶中
if(activeEffect){
track(target,key)
}
return target[key]
},
set(target,key,newValue){
target[key] = newValue
//触发副作用函数重新执行
trigger(target,key)
return true;//返回true代表设置成功
}
})
//测试
effect(function(){
console.log("effect run");
document.body.innerText = obj.test;
})
setTimeout(() => {
obj.test = "hello vue3"
obj.say="hello vue3"//触发输出effect run
},1000)