学习了王老师的课程后,在此对课程做一个记录,并借此捋顺思路方便后期复习,小白不懂,侵权的话联系删除
对象的响应式,是指对象的属性值改变时使用这个属性值的代码会自动执行
- m有一个初始化的值,有一段代码使用了这个值
- 那么在m有一个新的值时这段代码会自动重新执行
1、先上代码
清楚实现效果后,我们先来看一段代码
class Depend {
constructor() {
this.reactiveFns = []
}
// 反应
addDepend(...fns) {
this.reactiveFns.push(...fns)
}
// 通知
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
const dep = new Depend()
console.log(dep);
function watchFn(...fn) {
dep.addDepend(...fn)
}
watchFn(function(){
console.log("你好啊,李银河");
console.log("Hello world");
},function(){
console.log("锅姨~");
})
dep.notify()
上面的代码完成了依赖的收集,也就是说当有使用属性值的代码的时候就能够被通知到
- 逻辑是通过
Depend类中addDepend方法往reactiveFns数组中添加响应式函数,并通过notify方法执行。 - 完成以上代码后可以看出,只需要在对象属性值改变时运行
depend.notify()方法即可,我们此例中使用Proxy/Reflect代理来进行自动操作
2、管理依赖
然后便就需要考虑依赖的管理问题,我们现在是所有对象属性都共用一个depend,这是不对的,正确情况应该是不通对象的不同属性都应该各自对应一个depend
代码如下
class Depend {
constructor() {
this.reactiveFns = []
}
// 反应
addDepend(...fns) {
this.reactiveFns.push(...fns)
}
// 通知
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
// 响应式的对象
const obj = {
name: "why",
age: 18
}
// 封装一个获取depend的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 将对象和key还有依赖depend存起来
// 获取target对象的key
let map = targetMap.get(target)
if(!map) {
map = new Map()
targetMap.set(target, map)
}
// 通过key获取depend
let depend = map.get(key)
if(!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
// 将对象交给proxy代理
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
const depend = getDepend(target, key)
depend.addDepend(reactiveFn)
return Reflect.get(target, key, receiver)
},
set(target, key, newvalue, receiver) {
Reflect.set(target, key, newvalue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
// 封装一个响应函数
let reactiveFn = null
function watchFn(fn) {
reactiveFn = fn
fn()
reactiveFn = null
}
watchFn(function() {
console.log("aaaa",objProxy.name);
})
consloe.log("---------------修改值")
objProxy.name = "kobe"
- 我们首先new一个
weakMap,然后再封装一个获取depend的函数,在函数内部用首先声明的target来存对象和属性,再用map来存属性和depend,这样便能做到每一个对象下的每一个属性都拥有一个属于自己的depend,最后再将depen作为函数的返回值返回出去 - 然后我们需要在对象代理的
get方法中收集依赖depend.addDepend(),在set方法中执行依赖depend.notify() - 对于响应函数
watchFn我们也需要进行重写,首先需要将依赖函数赋值给提前声明的reactiveFn,方便在Proxy代理的get方法中使用,为了正确的收集依赖,所以watchFn函数传入的响应式函数需要执行一次fn()
3、重构Depend类
为了Proxy代理中能够简洁一点,get方法中获取值时只需要调用depend.addDepend()即可,不用判断reactiveFn中是否有值,所以我们需要对Depend类进行重构
let reactiveFn = null
class Depend {
constructor() {
this.reactiveFns = new Set()
}
/**
* Depend优化:
* 1.depend方法
* 2.使用Set保存依赖函数,而不是数组
* */
depend() {
if(reactiveFn) {
this.reactiveFns.add(reactiveFn)
}
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
- 使用
set数据结构保存依赖函数,防止重复添加依赖 - 在
Depend的depend()方法中执行判断逻辑即可 - 为了代码的复用,方便新添加的对象实现响应式,所以我们再封装一个
reactive函数
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepend(target, key)
// 给depend添加响应函数
depend.depend()
return Reflect.get(target, key, receiver)
},
set(target, key,newValue, receiver) {
Reflect.set(target, key,newValue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
}
// 对象的响应式
const obj = reactive({
name: 'why',
age: 18
})
到这响应式原理便基本完成,这里使用的数据代理实现的响应式,在Vue2中使用的是Object.defineProperty(),进行数据劫持完成响应式
最后总结一下:
- 首先我们需要一个
Depnd类来收集依赖,封装了一个watchFn函数来传入依赖函数 - 然后通过函数
getDepend对收集的依赖进行管理,在数据代理中添加依赖depend.depend()和执行depend.notify() - 最后为了方便新增函数使用响应式,所以再封装一个
reactive函数包裹数据代理的代码