一、什么是响应式原理
先看vue的代码:
当data里面的message发生改变,模板里的message也会进行更新,所以响应式是当某个变量发生改变,某些操作会自动执行
<template>
<div>
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: "hello world",
};
},
};
</script>
<style lang="scss" scoped></style>
二、vue3的响应式原理实现
分几个步骤来实现最终的响应式原理
1.响应式函数的封装
思路:
-
封装一个新的函数watchFn
-
凡是传入到watchFn的函数,就是需要响应式的
-
其他默认定义的函数都是不需要响应式的
-
我们可以声明一个数组来收集这些函数
缺点:
-
我们在实际开发中需要监听很多对象的响应式;
-
这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数;
-
我们不可能在全局维护一大堆的数组来保存这些响应函数;
const obj = {
name: 'kk',
age: 18
}
// 声明一个数组装需要响应的函数
const reactiveFns = []
// 监听函数 参数是函数 将函数push进reactiveArray
function watchFn(fn) {
reactiveFns.push(fn)
}
watchFn(function() {
const newName = obj.name
console.log("hello");
})
watchFn(function() {
console.log(obj.name, "demo function-----");
})
obj.name = 'zyk'
reactiveFns.forEach(fn => {
fn()
})
2.依赖收集类的封装
思路:
设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
const depend = new Depend()
function watchFn(fn) {
depend.addDepend(fn)
}
3.自动监听对象属性的变化
思路:使用Proxy为代理对象
// 自动监听属性值的变化
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)
depend.notify()
}
})
4.对象依赖管理的实现
// 封装一个depend函数
const targetMap = new WeakMap()
function getDepends(target, key) {
// 根据target对象获取map的过程
let map = targetMap.get(target)
// 判断map是否有属性 如果没有则创建一个map对象再将map对象和对应的对象存进WeakMap中
if(!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
// 判断depend是否有内容 如果没有则创建一个depend类 再将depend和对应的属性存进map中
if(!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
5.正确的依赖收集
之前收集依赖的地方是在 watchFn 中,但是这种收集依赖的方式我们根本不知道是哪一个key的哪一个depend需要收集依赖,只能针对一个单独的depend对象来添加依赖对象,在我们调用了Proxy的get捕获器时收集依赖,因为如果一个函数中使用了某个对象的key,那么它应该被收集依赖
// 封装一个响应函数
// 声明一个全局变量activeReactiveFn
let activeReactiveFn = null
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 自动监听属性值的变化
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepends(target, key)
// 给depend对象中添加响应函数
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set: function(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
const depend = getDepends(target, key) // 获取对应的对象里面的对应的一个depend
depend.notify()
}
})
6.总的思路
-
响应式函数的封装
-
声明一个全局变量activeReactiveFn
-
调用一次响应式函数
-
将全局变量activeReactiveFn 设为空
-
-
响应式依赖收集类的封装
-
封装一个Depend类,收集targetMap(响应的对象) -> map(对应的属性) -> depend(对应的响应式函数)
-
-
自动监听对象属性变化
-
封装一个获取depend对象的函数,对象依赖的管理,根据对象和它的key来获取对应的depend
代码示例:
// 保存当前需要保存的响应式函数
let activeReactiveFn = null
// 响应式依赖的收集
class Depend {
constructor() {
// 优化:保证不会上传同样的函数
this.reactiveFns = new Set()
}
depend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
// 收集依赖函数
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据对象获取map值
let map = targetMap.get(target)
// 第一次先创建map
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
}
// 监听对象的属性变量
function reactive(obj) {
// 返回一个proxy对象
return new Proxy(obj, {
get: function(target, key, receiver) {
// 获取depend对象
const depend = getDepend(target, key)
// 调用depend方法 将响应式函数push进this.reactiveFns
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 objProxy = reactive({
name: 'kk',
age: 18
})
const infoProxy = reactive({
name: 'ss',
address: '广州'
})
watchFn(function objNameFn() {
console.log(objProxy.name, '--------');
})
watchFn(function infoAddressFn() {
console.log(infoProxy.address);
})
objProxy.name = 'ss'
infoProxy.address = '北京'
三、vue2的响应式原理实现
vue3使用的proxy对象,vue2使用Object.defindProperty来监听对象属性的变化
只需要修改这一部分
// 创建出来一个函数,针对所有的对象都可以变成响应式对象
function reactive(obj){
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
get: function() {
const depend = getDepends(obj, key)
depend.depend()
return value
},
set: function(newValue) {
const depend = getDepends(obj, key)
value = newValue
depend.notify()
}
})
})
return obj
}