手把手实现vue响应式原理 -(六)
作者:互联网
1.什么是响应式
let m = 20
console.log(m)
console.log(m*2)
m = 40; 数据发生改变
- m有一个初始化的值,有一段代码使用了这个值
- 那么在 m 有一个新值的时候,这段代码可以自动重新执行
- 这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的
2.响应式函数的封装
// 对象的响应式
const obj = {
name: 'deyang',
age: 24
}
console.log('hello world')
console.log(obj.name)
obj.name = "superman"
对象的属性发生改变,我们希望涉及到该属性的代码能够重新执行,因此最好放到一个函数里面,
function foo() {
const newName = obj.name
console.log('hello world')
console.log(obj.name)
}
现在的问题就是当我的name属性发生改变的时候,foo函数能够重新执行。如果此时有另外一个函数 bar,但是这个函数我们不需要有响应式。
function bar() {
console.log('普通的其他函数')
console.log('这个函数不需要有任何的响应式');
}
所以我们的思路是封装一个响应式函数,将需要响应式的函数传递进去变成响应式。哪些函数需要响应式呢?产生了依赖的函数需要变成响应式。按照这个思路我们来完成响应式原理
// 封装一个响应式函数
let reactiveFns = [] // 定义存储响应式函数的数组
function watchFn(fn) {
reactiveFns.push(fn) // 将产生依赖的函数存入响应式
}
// 响应式对象
const obj = {
name: 'deyang',
age: 24
}
// 将依赖产生的函数变成响应式
watchFn(function foo() {
const newName = obj.name
console.log('hello world')
console.log(obj.name)
})
watchFn(function demo() {
console.log(obj.name, 'demo-------------')
})
function bar() {
console.log('普通的其他函数')
console.log('这个函数不需要有任何的响应式');
}
obj.name = "chendeyang" // 对象的属性发生改变,触发执行响应式函数
reactiveFns.forEach(fn => {
fn()
})
目前响应式第一步已经实现,当对象 name 发生改变,循环执行响应式数组内的函数并执行
但是目前用一个数组收集的只是 name 一个属性的依赖,但是obj 还有 age 属性的依赖呢,开发中又有其他的类,因此需要用一个有状态的工具类来收集依赖,当数据发生改变通过这个类来触发依赖
3.依赖收集类的封装
封装一个 Depend 的类,来收集和管理依赖,通过 addDepend 添加依赖,当依赖值发生改变的时候,通过 notify 来触发依赖
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
// 封装一个响应式函数
const depend = new Depend();
function watch(fn) {
depend.addDepend(fn)
}
watchFn(function demo() {
console.log(obj.name, 'demo-------------')
})
function bar() {
console.log('普通的其他函数')
console.log('这个函数不需要有任何的响应式');
}
obj.name = "chendeyang"
reactiveFns.forEach(fn => {
fn()
})
我们目前的代码还是手动调用 depend.notify() 来触发依赖
4.自动监听对象变化
我们想要当对象属性发生变化的时候自动触发依赖,就要监听对象属性的改变,前面的几篇讲了两个方法 proxy / Obejct.defineProperty,先实现vue3的响应式,当我们用 proxy 代理的时候,后续就不要对原来的 obj 对象进行操作了,而是通过代理对象。
// 对象的响应式
const obj = {
name: 'deyang', // depend对象
age: 24 // depend 对象
}
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
}
})
watchFn(function foo() {
const newName = objProxy.name
console.log('欢迎来到卢本伟广场')
console.log(objProxy.name)
})
objProxy.name = "superman"
当我们对 代理对象的name进行赋值的时候,就会进入到 set捕获器,我们就可以在 set 中进行依赖的触发
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.reactiveFns.push(fn)
}
// 对象的响应式
const obj = {
name: 'deyang', // depend对象
age: 24 // depend 对象
}
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
depend.notify()
}
})
watchFn(function foo() {
const newName = objProxy.name
console.log('hello world')
console.log(objProxy.name)
})
objProxy.name = "super"
objProxy.name = "man"
objProxy.name = "卢本伟"
原来name 发生改变,我们需要手动触发三次,现在就完成了自动触发
5.依赖收集的管理
针对上面的代码,如果我们产生了 age属性的依赖
watchFn(function () {
console.log(obj.age, '监听age的变化...1')
})
watchFn(function () {
console.log(obj.age, '监听age的变化...2')
})
objProxy.age = 111
在 age 属性发生变化的时候,它自动将 name 的依赖也自动触发了,这显然不是我们想看到的,如果另外还有一个 info 对象,以及info 对象的依赖,当一个依赖属性发生改变了,所有的依赖都会出发。因此我们需要对依赖进行管理。
正确的思路应该是:我们将依赖按照对象划分为 obj 和 info, 然后又根据属性划分 obj 和 info 下面的依赖
我们可以用到 ES6 的Map结构,为每一个响应式对象创建一个 map
const objMap = new Map()
objMap.set("name", "nameDepend")
objMap.set("age", "ageDepend")
const infoMap = new Map()
infoMap.set("address", "上海市")
然后我们将创建的map对象用 WeakMap 保存起来
const targetMap = new WeakMap()
targetMap.set(obj, objMap)
targetMap.set(info, infoMap)
这样做的好处是,如果 obj.name 发生改变了就可以通过 targetMap.get(obj) 获取 obj对象的 objMap,然后通过objMap.get(name) 获取存储的 name依赖,然后触发依赖
// obj.name 发生改变
const depend = targetMap.get(obj).get(name);
depend.notify()
按照上面的思路封装一个获取 depend 的函数
// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据 target 对象获取 map 的过程
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
}
// 对象的响应式
const obj = {
name: 'why', // depend对象
age: 24 // depend 对象
}
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// depend.notify()
const depend = getDepend(target, key)
console.log(depend.reactiveFns) // 此时打印四次 []
depend.notify()
}
})
watchFn(function foo() {
const newName = objProxy.name
console.log('hello world')
console.log(objProxy.name)
})
objProxy.name = "chen"
objProxy.name = "de"
objProxy.name = "yang"
objProxy.age = 111
但是此时触发依赖前,输出一下获取的依赖,其实都是空的,因为我们还没有正确的收集依赖
6.正确收集依赖
我们之前将传入的 watchFn() 的函数都放在了一个depend中,这显然是不对的,应该根据不同对象划分不同的depend,传入不同的depend
// 对象的响应式
const obj = {
name: 'why', // depend对象
age: 24 // depend 对象
}
const info = {
address: '上海' // depend
}
.....
// 用到了obj的属性就放到 obj 的 depend
watchFn(function() {
console.log('欢迎来到卢本伟广场')
console.log(objProxy.name)
})
// 用到了info的属性就放到 obj 的 depend
watchFn(function() {
console.log(infoProxy.address)
})
上面代码当运行到74行的时候,会走到 objProxy的get方法,那么我们就可以在这个捕获器里面获取depend,然后收集依赖
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
// 封装一个响应式函数
let activeReactiveFn = null
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取 depend 的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据 target 对象获取 map 的过程
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
}
// 对象的响应式
const obj = {
name: 'deyang', // depend对象
age: 24 // depend 对象
}
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
// 根据对应target获取对应depend
const depend = getDepend(target, key)
// depend中添加响应式函数
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// depend.notify()
const depend = getDepend(target, key)
depend.notify()
}
})
watchFn(function foo() {
console.log(objProxy.name, 'name depend 1')
})
watchFn(function foo() {
console.log(objProxy.name, 'name depend 2')
})
watchFn(function () {
console.log(objProxy.age, 'age depend 1')
})
watchFn(function () {
console.log(objProxy.age, 'age depend 2')
})
console.log('上面是默认执行的代码-------------------------')
objProxy.name = '李银河'
// objProxy.age = 10
目前就可以正确的收集依赖触发依赖,只有对应属性发生改变的时候才会触发依赖,
7.重构 Depend类
// 保存当前需要收集的响应式函数
let activeReactiveFn = null
/**
* Depend优化:
* 1.depend方法
* 2.使用Set来保存依赖函数,而不是使用数组[]
*/
class Depend {
constructor() {
// this.reactiveFns = []
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) {
// 根据 target 对象获取 map 的过程
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
}
// 对象的响应式
const obj = {
name: 'deyang', // depend对象
age: 24 // depend 对象
}
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
// 根绝对应target获取对应depend
const depend = getDepend(target, key)
// depend中添加响应式函数
// depend.addDepend(activeReactiveFn)
depend.depend()
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// depend.notify()
const depend = getDepend(target, key)
depend.notify()
}
})
watchFn(() => {
console.log(objProxy.name, '--------')
console.log(objProxy.name, '++++++++')
console.log(objProxy.name, '++++++++')
})
objProxy.name = 'haha'
8 最终版 vue3 / vue2
proxy
// 保存当前需要收集的响应式函数
let activeReactiveFn = null
class Depend {
constructor() {
// this.reactiveFns = []
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) {
// 根据 target 对象获取 map 的过程
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
}
function defineReactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 根绝对应target获取对应depend
const depend = getDepend(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// depend.notify()
const depend = getDepend(target, key)
depend.notify()
}
})
}
// 对象的响应式
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const objProxy = defineReactive({
name: 'chendeyang', // depend对象
age: 24 // depend 对象
})
const infoProxy = defineReactive({
address: '上海市',
height: 1.88
})
watchFn(() => {
console.log(infoProxy.address)
})
infoProxy.address = '武汉市'
Object.defineProperty
// 保存当前需要收集的响应式函数
let activeReactiveFn = null
class Depend {
constructor() {
// this.reactiveFns = []
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) {
// 根据 target 对象获取 map 的过程
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
}
function defineReactive(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
}
// 对象的响应式
// 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2)
const obj = defineReactive({
name: 'chendeyang', // depend对象
age: 24 // depend 对象
})
const info = defineReactive({
address: '上海市',
height: 1.88
})
watchFn(() => {
console.log(info.address)
})
info.address = '武汉市'
标签:vue,depend,name,const,手把手,响应,key,obj,target 来源: https://blog.csdn.net/frontendchen/article/details/121941179