本文已参与「新人创作礼」活动,一起开启掘金创作之路。
关于 Proxy
众所周知,vue3使用的是Proxy去代理对象。MDN官方是这样说的
ps:在js中,数组和函数也是对象,Proxy也可以进行代理 点击可了解更多--Poxy
具体用法
将obj通过Proxy进行代理
let obj={
flag:true,
count:1,
name:"zhangsan"
}
const newObj= new Proxy(obj,{
get(target,key){
return Reflect.get(target,key)
},
set(arget,key,value){
Reflect.set(target,key,value)
}
return true
})
console.log(newObj)
newObj打印如下
vue3响应式原理
vue3响应式原理:使用Proxy进行代理。在代理的get阶段即读取obj属性阶段,收集依赖,在set阶段即设置obj属性阶段,触发依赖。
具体如何去收集依赖和触发依赖呢?举个🌰
目标如下:当我们通过修改 newObj.name=”lisi“的值,页面上也需要更新为lisi
1.现在我们创建一个index.js,代码如下
let obj={
flag:true,
count:1,
name:"zhangsan"
}
const newObj= new Proxy(obj,{
get(target,key){
return Reflect.get(target,key)
},
set(arget,key,value){
Reflect.set(target,key,value)
}
return true
})
function effect(){
document.getElementById("app").textContent=newObj.name
}
effect()
setTimeout(()=>{
newObj.name="lisi"
},2000)
2.创建一个index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./index.js" async></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
分析: 在这里,我们执行effect函数
读取:会将newObj.name的值设置为id="app"元素的文本内容,会触发代理的getter,我们可以将我们的effect函数收集起来
设置: newObj.name="lsii",会触发代理的setter,我们可以再次执行effect函数,这样就会再次更新id="app"元素的文本内容 思路:
我们定义一个数组---effects,收集依赖
//1index.js
let obj={
flag:true,
count:1,
name:"zhangsan"
}
let effects=[]
const newObj= new Proxy(obj,{
get(target,key){
//新增代码:收集依赖
effects.push(effect)
return Reflect.get(target,key)
},
set(arget,key,value){
Reflect.set(target,key,value)
}
//新增代码:触发依赖
effects.forEach(effect=>{
effect()
})
return true
})
function effect(){
document.getElementById("app").textContent=newObj.name
}
effect()
setTimeout(()=>{
newObj.name="lisi"
},2000)
效果如下:
问题:
终于实现了! 😄 😄 但是这样收集依赖会有一些问题。
1.effect函数是硬编码,如果不是effect函数该怎么办?
2.目前我是将收集的依赖直接push进effects里面,如果多次读取newObj的不同的属性,触发依赖的时候,我们怎么知道要执行哪个effects呢?
基于以上两个问题
思路:
1.我们可以将要收集的依赖作为fn传入effect里面
2.重新设计要收集依赖的数据结构,使收集依赖时,可以知道收集的是哪个属性的依赖,触发依赖的时候,知道触发的是谁的依赖
//1index.js
...
let activeEffect=null //定义当前正在收集的依赖函数
function effect(fn){
activeEffect=fn
fn()
}
effect(()=>{
document.getElementById("app").textContent=newObj.name
})
setTimeout(()=>{
newObj.name="lisi"
},2000)
以上代码解决了effect硬编码的问题
接下来解决第二个问题 我们收集依赖的时候,可以根据target-key-effect 的树状结构去设计数据 比如
erDiagram
target ||--o{ key1 : get
key1 ||--|{ effect1 : get
target ||--o{ key2 : get
key2 ||--|{ effect2 : get
为了代码结构的清晰,我们可以单独把收集依赖和触发依赖的功能,单独抽离出来,分别是track(),trigger()
//1index.js
let obj={
flag:true,
count:1,
name:"zhangsan"
}
let bucket=new weakMap()//
const newObj= new Proxy(obj,{
get(target,key){
//新收集依赖
track(target,key)
return Reflect.get(target,key)
},
set(arget,key,value){
Reflect.set(target,key,value)
//新触发依赖
trigger(target,key)
return true
}
})
//依赖收集
function track(target,key){
let depMaps=bucket.get(target)
if(!depMaps){
bucket.set(target,(depMaps=new Map()))
}
let deps=depMaps.get(key)
if(!deps){
depMaps.set(key,(deps=new Set()))
}
deps.add(activeEffect)
}
//触发依赖
function trigger(target,key){
let depMaps=bucket.get(target)
let deps=depMaps.get(key)
deps && deps.forEach(effect => {
effect()
});
}
...
完整代码如下
//1index.js
let obj={
flag:true,
count:1,
name:"zhangsan"
}
let activeEffect=null //定义当前正在收集的依赖函数
let bucket=new weakMap()//
const newObj= new Proxy(obj,{
get(target,key){
//新收集依赖
track(target,key)
return Reflect.get(target,key)
},
set(arget,key,value){
Reflect.set(target,key,value)
//新触发依赖
trigger(target,key)
return true
}
})
//依赖收集
function track(target,key){
let depMaps=bucket.get(target)
if(!depMaps){
bucket.set(target,(depMaps=new Map()))
}
let deps=depMaps.get(key)
if(!deps){
depMaps.set(key,(deps=new Set()))
}
deps.add(activeEffect)
}
//触发依赖
function trigger(target,key){
let depMaps=bucket.get(target)
let deps=depMaps.get(key)
deps && deps.forEach(effect => {
effect()
});
}
//effect相关
function effect(fn){
activeEffect=fn
fn()
}
effect(()=>{
document.getElementById("app").textContent=newObj.name
})
setTimeout(()=>{
newObj.name="lisi"
},2000)
这样,我们在收集依赖和触发依赖的时候,就知道,收集的是哪个对象,哪个属性的依赖,在触发依赖的时候,依次通过bucket-- target----key -- effect 的方式获取到索要触发的函数啦~