1.思路分析
响应式即数据发生变化时,页面自动更新,从数据层面来说,当某个数据发生改变时,依赖于该数据的关联函数全部重新执行,以达到重新渲染视图的效果
2.关键实现的四个类/函数
2.1 依赖类
该类作用是收集依赖和通知更新
包含一个属性:该属性用于存放某个数据对应的所有依赖,这里用集合表示,保证每个依赖只能添加一次;
包含两个方法:
(1)depend方法:用于向保存依赖的集合添加依赖函数,即某个函数访问到一个数据时,这个函数就是此数据的依赖
(2)notify方法:遍历保存依赖的集合,依次调用该集合中的依赖函数,即当数据发生改变时,将所有依赖执行一遍,以更新视图
let activeReactiveFn = null
class Depend {
constructor() {
// 使用集合,保证依赖只添加一次
this.reactiveFns = new Set()
}
depend(){
if(activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
notify() {
this.reactiveFns.forEach(fn => fn())
}
}
2.2 watchFns函数
该函数用于收集依赖,接受一个函数为入参,执行该函数以确保数据在一开始就被添加到响应式系统
function watchFns (fn) {
activeReactiveFn = fn
// 第一次就执行一次,以收集依赖
fn()
activeReactiveFn = null
}
2.3 getDepend函数
该函数用于获取对应对象的depend实例,目的是为了保证每个对象的每个key都对应一个唯一的depend实例,以便在对象的key发生变化时能够正确地收集依赖和通知更新。同时,使用WeakMap来存储depend实例,可以避免对象的key被占用,也可以在对象被垃圾回收时自动清除对应的depend实例。
// 获取depend的函数
// 使用weakmap,key必须是一个对象
let targetMap = new WeakMap()
function getDepend (target, key) {
// 1.根据target取出对应的map对象
let map = targetMap.get(target)
if(!map) {
map = new Map()
targetMap.set(target, map)
}
// 2.根据各对象的key取出具体的depend
let depend = map.get(key)
if(!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
2.4 reactive函数
该函数的作用是把传入的对象变为一个响应式的对象
2.4.1 vue3的响应式
const p = new Proxy(target, handler);
Vue3使用Proxy对对象进行代理,p为代理后的对象,如果访问p的属性,即执行handler对象中的get方法,在此时获取该属性对应的依赖,并执行depend实例的添加依赖方法;如果改变代理对象的属性,即执行handler对象中的set方法,在此时获取该属性对应的依赖,并执行depend实例触发依赖
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const depend = getDepend(target, key)
depend.depend()
// Reflect可以方便的操作对象,这里用于根据key获取值
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
}
2.4.2 vue2的响应式
Vue2使用Object.defineProperty对数据进行劫持,访问对象时会执行get方法,此时添加依赖;改变对象时会执行set方法,此时执行依赖
function reactive(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
get() {
const depend = getDepend(obj, key)
depend.depend()
return value
},
set(newValue) {
value = newValue
const depend = getDepend(obj, key)
depend.notify()
}
})
})
return obj
}
3.整合代码及简单测试
let activeReactiveFn = null
class Depend {
constructor() {
this.reactiveFns = new Set()
}
depend(){
if(activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
notify() {
this.reactiveFns.forEach(fn => fn())
}
}
function watchFns (fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
let 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
}
// Vue3写法
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const depend = getDepend(target, key)
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()
}
})
}
// 测试代码
let objProxy = reactive({
name: 'zjm',
age: 18
})
let infoProxy = reactive({
info: 'good'
})
watchFns(() => {
console.log(objProxy.name)
console.log(objProxy.age)
})
objProxy.name = '123'
infoProxy.info = 'bad'