《Vue进阶教程》第十四课:改进桶结构
往期内容:
《Vue进阶教程》第三课:Vue响应式原理
《Vue进阶教程》第四课:reactive()函数详解
《Vue进阶教程》第五课:ref()函数详解(重点)
《Vue进阶教程》第六课:computed()函数详解(上)
《Vue进阶教程》第七课:computed()函数详解(下)
《Vue进阶教程》第八课:watch()函数的基本使用
《Vue进阶教程》第九课:watch()函数的高级使用
《Vue进阶教程》第十课:其它函数
《Vue进阶教程》第十一课:响应式系统介绍
《Vue进阶教程》第十二课:实现一对多
《Vue进阶教程》第十三课:实现依赖收集
一、改进桶结构
看起来现在可以自动收集依赖. 但是依然解决不了不同的属性对应不同的副作用函数集合这个问题.
因此, 我们需要改进桶结构
将桶
改造成一个Map映射表, 不同的属性对应不同的Set集合
示例
reactive.js
// 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
const bucket = new Map() // 修改 [name: Set(fn, fn), age: Set(fn, fn)]
// 定义一个全局变量, 保存当前正在执行的副作用函数
let activeEffect = null
function isObject(value) {
return typeof value === 'object' && value !== null
}
// 收集依赖
function track(target, key) {
if (!activeEffect) return
let depSet = bucket.get(key)
if (!depSet) {
depSet = new Set()
bucket.set(key, depSet)
}
// 只有activeEffect有值时(保存的副作用函数), 才添加到桶中
depSet.add(activeEffect)
}
function trigger(target, key) {
// 从副作用函数桶中依次取出每一个元素(副作用函数)执行
let depSet = bucket.get(key)
if (depSet) {
depSet.forEach((fn) => fn())
}
}
/**
* 创建响应式数据
* @param [object]: 普通对象
* @return [Proxy]: 代理对象
*/
function reactive(data) {
if (!isObject(data)) return
return new Proxy(data, {
get(target, key) {
// 在get操作时, 收集依赖
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
// 在set操作时, 触发副作用重新执行
trigger(target, key)
return true
},
})
}
/**
* 注册副作用函数
* @param [function]: 需要注册的 副作用函数
*/
function effect(fn) {
if (typeof fn !== 'function') return
// 记录正在执行的副作用函数
activeEffect = fn
// 调用副作用函数
fn()
// 重置全局变量
activeEffect = null
}
至此, 我们通过改进桶结构, 可以区分同一个代理对象的不同的属性
二、进一步改进桶结构
🤔思考
如果不同的源对象存在同名属性, 就会出现问题
比如
pState代理的源对象上存在name
属性
pState1代理的源对象上也存在name
属性
这样, 在bucket桶里, 就不能区分不同的代理对象
问题示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// 定义一个副作用桶bucket
const bucket = new Map() // 修改
// 定义一个全局变量, 作于保存 `当前副作用函数`
let activeEffect = null
// 收集依赖
function track(target, key) {
// 根据不同的key, 获取对应的集合
let depSet = bucket.get(key)
if (!depSet) {
// 如果不存在, 创建一个新的集合, 并添加到桶中
depSet = new Set()
bucket.set(key, depSet) // 建立 key -> Set的对应关系
}
depSet.add(activeEffect) // 将当前副作用函数添加到对应的集合
}
function trigger(target, key) {
let depSet = bucket.get(key)
if (depSet) {
// 如果对应的集合存在, 遍历集合中的每个函数
depSet.forEach((fn) => fn())
}
}
/**
* 定义响应式
* @param [object] : 普通对象
* @return [Proxy] : 代理对象
*/
function reactive(data) {
// 如果传入的data不是一个普通对象, 不处理
if (typeof data !== 'object' || data == null) return
return new Proxy(data, {
get(target, key) {
// console.log(`自定义访问${key}`)
if (activeEffect != null) {
// 收集依赖
track(target, key)
}
return target[key]
},
set(target, key, value) {
// console.log(`自定义设置${key}=${value}`)
target[key] = value // 先更新值
// 触发更新
trigger(target, key)
return true
},
})
}
/**
* 注册副作用函数
* @params [function]: 要注册的 副作用函数
*/
function registEffect(fn) {
if (typeof fn !== 'function') return
// 将当前注册的副作用函数 保存 到全局变量中
activeEffect = fn
// 执行当前副作用函数, 收集依赖
fn()
// 重置全局变量
activeEffect = null
}
const pState = reactive({ name: 'hello', age: 20 })
const pState1 = reactive({ name: 'p1' })
registEffect(function effectFn() {
console.log('effectFn...', pState.name)
})
registEffect(function effectFn1() {
console.log('effectFn1...', pState1.name)
})
console.log(bucket)
setTimeout(() => {
pState.name = 'brojie'
}, 1000)
</script>
</body>
</html>
解决方案
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// 定义一个副作用桶bucket
const bucket = new WeakMap() // 修改
// 定义一个全局变量, 作于保存 `当前副作用函数`
let activeEffect = null
// 收集依赖
function track(target, key) {
// 根据不同的target, 获取对应的Map
let depMap = bucket.get(target)
if (!depMap) {
depMap = new Map()
bucket.set(target, depMap) // 建立target -> Map的对应关系
}
// 根据不同的key, 获取对应的集合
let depSet = depMap.get(key)
if (!depSet) {
// 如果不存在, 创建一个新的集合
depSet = new Set()
depMap.set(key, depSet) // 建立 key -> Set的对应关系
}
depSet.add(activeEffect) // 将当前副作用函数添加到对应的集合
}
function trigger(target, key) {
let depMap = bucket.get(target)
if (!depMap) return
let depSet = depMap.get(key)
if (depSet) {
// 如果对应的集合存在, 遍历集合中的每个函数
depSet.forEach((fn) => fn())
}
}
/**
* 定义响应式
* @param [object] : 普通对象
* @return [Proxy] : 代理对象
*/
function reactive(data) {
// 如果传入的data不是一个普通对象, 不处理
if (typeof data !== 'object' || data == null) return
return new Proxy(data, {
get(target, key) {
// console.log(`自定义访问${key}`)
if (activeEffect != null) {
// 收集依赖
track(target, key)
}
return target[key]
},
set(target, key, value) {
// console.log(`自定义设置${key}=${value}`)
target[key] = value // 先更新值
// 触发更新
trigger(target, key)
return true
},
})
}
/**
* 注册副作用函数
* @params [function]: 要注册的 副作用函数
*/
function registEffect(fn) {
if (typeof fn !== 'function') return
// 将当前注册的副作用函数 保存 到全局变量中
activeEffect = fn
// 执行当前副作用函数, 收集依赖
fn()
// 重置全局变量
activeEffect = null
}
const pState = reactive({ name: 'hello', age: 20 })
registEffect(function effectName() {
console.log('effectName...', pState.name)
})
registEffect(function effectAge() {
console.log('effectAge...', pState.age)
})
console.log(bucket)
setTimeout(() => {
pState.name = 'brojie'
}, 1000)
</script>
</body>
</html>
至此, 我们可以区分不同的代理对象下不同属性对应的副作用函数集合.
真正实现了完善的桶结构~✌
如果学到这里, 恭喜你, 已经实现了一个可用的响应式系统! ^_^😃
三、小结
💡 小结
1在get时收集依赖: 收集不同代理对象不同属性所依赖的副作用函数
2在set时触发依赖: 取出当前属性所依赖的所有副作用函数, 重新执行