响应式指的是组件 data 的数据一旦变化,立刻触发视图的更新。它是实现数据驱动视图的第一步。在我现阶段的理解里,响应式就是当某一个数据发生变化时,变化到一定程度,与之相关的函数方法会自动调用和执行。
一、响应式函数的封装
使用数组封装要执行的函数,当对象的某个属性发生变化时,遍历函数,执行函数。如代码所示:
//1,创建对象
const obj = {
name:"blackpink",
age:20,
sing: "pink"
}
// 2,对象发生变化时,所要执行的函数
watchFn(function(){
console.log(obj.name)
console.log("blackpink出新歌了")
})
watchFn(function(){
console.log(obj.name)
console.log(obj.name+"在跳舞")
})
watchFn(function(){
console.log(obj.age)
console.log(obj.age+"在跳舞")
})
// 3,创建一个数组,用来封装函数
let reactiveFns = []
function watchFn(fn){
reactiveFns.push(fn)
}
// 4,改变对象的某一个属性,然后遍历函数执行
obj.name="jennie"
reactiveFns.forEach(fn=>{
fn()
})
二、依赖收集类的封装
使用数组封装函数有一个缺点,就是对象的某一属性值,所有的函数都会重新执行一遍,很多无关的函数也会执行。那么,如何将对象的某一属性变化时,只改变跟这个属性相关的函数呢,比如name改变时,只执行跟name有关的函数。age改变时,只执行跟age有关的函数。那么,就需要用到depend,给对象的每一属性都各自添加一个depend,namedepend,agedepend。要想实现这样的效果,先要创建一个依赖收集类,如下图代码的例子,但是这是对整体的一个依赖,并没有监听到具体的某一个属性。当对象的某一个属性变化时,依然会依次执行所有函数,但是这一步骤依赖类的封装是自动监听对象变化的前提。
1.创建一个depend类
class Depend{
constructor(){
this.reactiveFns =[]
}
addDepend(reactiveFn){
this.reactiveFns.push(reactiveFn)
}
notify(){
this.reactiveFns.forEach(fn=>{
fn()
})}
}
2.new一个depend对象
const objdepend = new Depend()
function watchFn(fn){
objdepend.addDepend(fn)
}
3.发生改变时,调用
obj.name="jennie"
objdepend.notify()
三、自动监听对象变化
自动监听,需要对对象new一个proxy,在proxy里面使用get,set方法监听属性的变化。使用这个方法的目的是,当对象的属性发生变化时,代理对象proxy能使用set方法检测到属性的变化,重新赋值,然后notify方法,自动执行所有函数。但是这一步还是对整体的一个依赖,并没有监听到具体的某一个属性。当对象的某一个属性变化时,依然会依次执行所有函数,到这一步还是没有对对象的各个属性分别做一个依赖。以下代码还是无论对象的哪一个对象发生变化,所有的函数都会执行一遍。
const objProxy = new Proxy(obj,{
get:function(target,key,receiver){
return Reflect.get(target,key,receiver)
},
set:function(target,key,newValue,receiver){
Reflect.set(target,key,newValue,receiver)
objdepend.notify()
}
})
四、依赖收集的管理
那么什么是依赖收集的管理呢,就是分别给不同对象的不同属性分别添加一个依赖,使得当一个对象的某个属性发生变化时,会根据它的依赖自动执行所有跟这个属性相关的函数,其它的函数保持不变。那么如何实现呢?这就需要用到我们的Map对象,
//1,new一个map存储属性和属性专属的depend
const objMap = new Map()
objMap.set("name","nameDepend")
objMap.set("age","ageDepend")
console.log(objMap)
//2,new一个weakmap存储对象的对象的map.
const targetMap = new WeakMap()
//3.然后就可以通过targetmap,找到objmap,然后找到相应的属性依赖
console.log(targetMap.get(obj).get("name"));
const depend = targetMap.get(obj).get("name")
将以上步骤封装到一个函数里面。
const targetMap = new WeakMap()
function getDepend(target,key){
let map = targetMap.get(target)
if(!map){
map = new Map()
targetMap.set(target,map)
}
let depend = map.get(key)
if(!depend){
depend = new Depend()
map.set(key,depend)
}
return depend
}
以上步骤就是通过传入,对象和对象的某一个属性,通过weakmap找到对应的map对象,再通过map的key找到对应的depend过程。 但是,现在暂时还没有将对象的某个属性相关的函数传进去,也就是,当变量发生变化时,没有函数执行,必须将函数添加到对应依赖depend上,才能够执行函数。
五、正确的收集依赖
let activeReactiveFn = null
function watchFn(fn){
// 函数必须执行,才能知道函数里面用到了哪些对象的哪些属性
activeReactiveFn = fn
fn()
activeReactiveFn = null
/* watchFn(function(){
console.log(objProxy.sing+"---------sing发生变化需要执行的")
console.log("blackpink出新歌了")
}) */
// 假设执行的是上面这个函数,那么执行时,会访问到proxy的get方法,根据target,key获取对应的depend,
/*const depend = getDepend(target,key),然后通过depend.adddpend()拿到函数
这个函数不好加,需要在外面赋值给一个全局变量,然后执行,如果访问到proxy的get,就将该函数的全局变量添加到adddepend里面
// 1.在此找到depend对象,然后将depend对应属性相关的函数依次传入 */
// depend.addDepend(fn)
}
六、优化
优化一、不在get方法里使用多余的变量
// depend.addDepend(activeReactiveFn ) // 优化:不让get方法关联到activeReactive,给depend类添加一个depend方法,如果有调用activereactionfn,则将函数 添加到依赖对应的函数里面
new depend{depend(){
if(activereactivefn){
this,reactivefns.push(activereactivefn)
}
}}
depend.depend();
优化二、函数中某一个对象属性出现两次,但变量只改变一次时,函数也只执行一次,使用数组,数组会将proxy传入的相同函数都保存,会执行两次。而使用set方法,相同的函数set只存储一次。
constructor(){
// this.reactiveFns = []
this.reactiveFns = new Set()
// 使用new set(),是为了当函数里面出现两次相同对象的相同属性时,该函数只会调用一次,set方法就是不能存储相同的值的数组.
}
// addDepend(reactiveFn){
// this.reactiveFns.add(reactiveFn)
// }
depend(){
if(activeReactiveFn){
this.reactiveFns.add(activeReactiveFn)
}
}
七、新增对象,新增响应式。
const info = {
name:"twice",
age:18
}
const infoproxy = new Proxy(info,{
get:function(target,key,receiver){//get方法中收集对应的函数
const depend = getDepend(target,key)
// depend.addDepend(activeReactiveFn )//获取执行函数中对应属性的相关函数
// 优化:不让get方法关联到activeReactive,给depend类添加一个depend方法,如果有调用activereactionfn,则将函数
// 添加到依赖对应的函数里面
depend.depend();
return Reflect.get(target,key,receiver)
},
set:function(target,key,newValue,receiver){
Reflect.set(target,key,newValue,receiver)
const depend = getDepend(target,key)//获取执行函数中对应属性的依赖
depend.notify()
}
})
watchFn(function(){
console.log(infoproxy.name+"比不过blackpink")
})
infoproxy.name = "BTS";
如上代码所示,每添加一个对象,需要创建一个新的响应式对象,代码非常多,这样用起来非常麻烦,所以需要将这个步骤变成自动添加响应式。
八、分别给不同的对象调用函数自动添加响应式
function reactive(obj){
return new Proxy(obj,{
get:function(target,key,receiver){
const depend = getDepend(target,key)
depend.addDepend(activeReactiveFn )/
depend.depend();
return Reflect.get(target,key,receiver)
},
set:function(target,key,newValue,receiver){
Reflect.set(target,key,newValue,receiver)
const depend = getDepend(target,key)
depend.notify()
}
})
}
const infoproxy = reactive(info);
以上就是我刚开始接触vue3对响应式原理的理解和梳理,希望之后有案例,做项目能够理解的更加透彻,欢迎大佬指点一二!