Vue3响应式原理实现
1、响应式的概念
比如对象的属性有一个初始化的值,有一段代码使用了这个值、那么在对象中的属性发生变化时,这段代码可以自动重新执行
2、响应式函数实现
把与对象属性相关函数 (下面称为对象的依赖) 放到一个数组中
如果属性发生变化会重新执行属性的依赖函数
let reactiveFns = []
function watchFn(fn) {
reactiveFns.push(fn)
}
let obj = {
name: 'lmw',
}
watchFn(function () {
console.log('----------', obj.name)
})
// 如果修改了obj.name中的值 那么 会把与obj.name有关的函数再执行一遍
// 例如
obj.name = 'wml'
reactiveFns.forEach((fn) => {
fn()
})
3、对依赖收集进行封装
上面写的代码只能收集name一个属性,但是在开发中所有对象不可能都只有一个属性,每一个属性就多写一个数组来收集这显然是不现实的,所以这里我们可以封装一个类来收集
class Depend {
constructor() {
// 用于存放依赖
this.reactiveFns = []
}
addDepend(fn) {
// 添加依赖的方法
this.reactiveFns.push(fn)
}
notify() {
// 执行依赖的方法
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
// let reactiveFns = []
const depend = new Depend()
function watchFn(fn) {
depend.addDepend(fn)
}
let obj = {
name: 'lmw',
}
watchFn(function () {
console.log('----------', obj.name)
})
obj.name = 'wml'
depend.notify()
// reactiveFns.forEach((fn) => {
// fn()
// })
4、自动监听对象属性的变化
每次操作完对象属性的值时都要手动再执行一遍收集的依赖,这样很麻烦,所以我们可以用Proxy来监听这个对象
class Depend {
constructor() {
// 用于存放依赖
this.reactiveFns = []
}
addDepend(fn) {
// 添加依赖的方法
this.reactiveFns.push(fn)
}
notify() {
// 执行依赖的方法
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
const depend = new Depend()
function watchFn(fn) {
depend.addDepend(fn)
}
let obj = {
name: 'lmw',
age: 18,
}
const objProxy = new Proxy(obj, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set: function (target, key, receiver) {
Reflect.set(target, key, receiver)
depend.notify()
},
})
watchFn(function () {
// console.log('----------', obj.name)
console.log('----------', objProxy.name)
})
watchFn(function () {
console.log('----------', objProxy.age)
})
// obj.name = 'wml'
objProxy.name = 'wml'
objProxy.age = 17
5、依赖收集管理
很显然如果代码这样我们不管改变对象的任何属性都是会执行一遍所有的依赖的,这并不是我们想看的,在这我们可以使用Map和WeakMap来对我们收集的依赖进行管理
let dependFn = null // 用于给Proxy中的set传入依赖函数
class Depend {
constructor() {
// 用于存放依赖
this.reactiveFns = []
}
addDepend(fn) {
// 添加依赖的方法
this.reactiveFns.push(fn)
}
notify() {
// 执行依赖的方法
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
const depend = new Depend()
function watchFn(fn) {
dependFn = fn
fn()
dependFn = null
}
let obj = {
name: 'lmw',
age: 18,
}
let info = {
sex: '女',
}
const weakMap = new WeakMap()
function getDepend(target, key) {
let map = weakMap.get(target)
if (!map) {
map = new Map()
weakMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const objProxy = new Proxy(obj, {
get: function (target, key, receiver) {
const ownDepend = getDepend(target, key)
ownDepend.addDepend(dependFn)
return Reflect.get(target, key, receiver)
},
set: function (target, key, receiver) {
Reflect.set(target, key, receiver)
const ownDepend = getDepend(target, key)
ownDepend.notify()
},
})
const infoProxy = new Proxy(obj, {
get: function (target, key, receiver) {
const ownDepend = getDepend(target, key)
ownDepend.addDepend(dependFn)
return Reflect.get(target, key, receiver)
},
set: function (target, key, receiver) {
Reflect.set(target, key, receiver)
const ownDepend = getDepend(target, key)
ownDepend.notify()
},
})
watchFn(function () {
console.log('----------', objProxy.name)
})
watchFn(function () {
console.log('----------', objProxy.age)
})
watchFn(function () {
console.log('++++++++++', infoProxy.sex)
})
console.log('---------------')
objProxy.name = 'wml'
objProxy.age = 17
infoProxy.sex = '男'
6、代码优化
1、每当我们多一个对象时就要有对应的Proxy,这样代码太过与臃肿,我们可以将返回Proxy代理的操作封装成一个函数
2、Proxy中的set每次添加依赖到ownDepend中都要传入dependFn,这样太麻烦,我们可以将这个方法封装到depend类中
3、在收集依赖时如果依赖的函数调用了两次相同的属性那么会重复执行代码,这里可以采用使用Set的数据结构来储存依赖
let dependFn = null // 用于给Proxy中的set传入依赖函数
class Depend {
constructor() {
// 用于存放依赖
this.reactiveFns = new Set()
}
// addDepend(fn) {
// // 添加依赖的方法
// this.reactiveFns.add(fn)
// }
addDepend() {
// 添加依赖的方法
if (dependFn) {
this.reactiveFns.add(dependFn)
}
}
notify() {
// 执行依赖的方法
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
const depend = new Depend()
function watchFn(fn) {
dependFn = fn
fn()
dependFn = null
}
let obj = {
name: 'lmw',
age: 18,
}
let info = {
sex: '女',
}
const weakMap = new WeakMap()
function reactice(obj) {
return new Proxy(obj, {
get: function (target, key, receiver) {
const ownDepend = getDepend(target, key)
ownDepend.addDepend()
return Reflect.get(target, key, receiver)
},
set: function (target, key, receiver) {
Reflect.set(target, key, receiver)
const ownDepend = getDepend(target, key)
ownDepend.notify()
},
})
}
function getDepend(target, key) {
let map = weakMap.get(target)
if (!map) {
map = new Map()
weakMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const objProxy = reactice(obj)
const infoProxy = reactice(info)
watchFn(function () {
console.log('----------', objProxy.name)
})
watchFn(function () {
console.log('----------', objProxy.age)
})
watchFn(function () {
console.log('++++++++++', infoProxy.sex)
})
console.log('---------------')
objProxy.name = 'wml'
objProxy.age = 17
infoProxy.sex = '男'