《Vue进阶教程》第十六课:深入完善响应式系统之单例模式
往期内容:
《Vue进阶教程》第五课:ref()函数详解(重点)
《Vue进阶教程》第六课:computed()函数详解(上)
《Vue进阶教程》第七课:computed()函数详解(下)
《Vue进阶教程》第八课:watch()函数的基本使用
《Vue进阶教程》第九课:watch()函数的高级使用
《Vue进阶教程》第十课:其它函数
《Vue进阶教程》第十一课:响应式系统介绍
《Vue进阶教程》第十二课:实现一对多
《Vue进阶教程》第十三课:实现依赖收集
《Vue进阶教程》第十四课:改进桶结构
《Vue进阶教程》第十五课:深入完善响应式系统之模块化
🤔思考
- 对于同一个源对象每次调用reactive返回的代理对象应该是一样的
- 对于一个已经代理过的对象再次代理应该返回的也应该是一样的
1) 实现单例
为了实现单例, 我们需要建立源对象
->代理对象
的映射关系
- 如果存在映射, 说明已经代理过了, 直接返回
- 如果不存在映射, 说明没有代理过, 创建一个新的代理对象返回
定义源对象
->代理对象
的映射表(使用WeakMap)
reactive.js
// 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
// 修改 [state -> Map[name: Set(fn, fn), age: Set(fn, fn)], state1 -> Map]
const bucket = new WeakMap()
// 建立一个映射表 target -> proxy
const reactiveMap = new WeakMap() // 新增
// 定义一个全局变量, 保存当前正在执行的副作用函数
let activeEffect = null
function isObject(value) {
return typeof value === 'object' && value !== null
}
// 收集依赖
function track(target, key) {
// 只有activeEffect有值时(保存的副作用函数), 才添加到桶中
if (!activeEffect) return
let depMap = bucket.get(target)
if (!depMap) {
depMap = new Map()
bucket.set(target, depMap)
}
let depSet = depMap.get(key)
if (!depSet) {
depSet = new Set()
depMap.set(key, depSet)
}
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) {
if (!isObject(data)) return
// 如果映射表中存在了对应关系
if (reactiveMap.has(data)) {
// 返回data对应的代理对象
return reactiveMap.get(data)
}
const proxy = 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
},
})
// 建立data(源对象)和proxy(代理对象)的映射关系
reactiveMap.set(data, proxy)
return proxy
}
/**
* 注册副作用函数
* @param [function]: 需要注册的 副作用函数
*/
function effect(fn) {
if (typeof fn !== 'function') return
// 记录正在执行的副作用函数
activeEffect = fn
// 调用副作用函数
fn()
// 重置全局变量
activeEffect = null
}
测试用例
<!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>
<script src="./reactive.js"></script>
</head>
<body>
<script>
const source = { name: 'hello' }
const p = reactive(source)
const p1 = reactive(source)
console.log(p === p1) // true
</script>
</body>
</html>
2) 实现重复代理
可以定义一个特殊的标识__v_isReactive
- 如果存在该标识, 说明已经代理过, 直接返回
- 如果不存在该标识, 说明没有被代理, 创建新的代理对象
示例
// 定义源对象->代理对象映射表
const reactiveMap = new WeakMap()
// 定义一个副作用桶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)
}
let depSet = depMap.get(key)
if (!depSet) {
depSet = new Set()
depMap.set(key, depSet)
}
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] : 代理对象
*/
export function reactive(data) {
// 如果传入的data不是一个普通对象, 不处理
if (typeof data !== 'object' || data == null) return
if (reactiveMap.has(data)) {
// 返回data对应的代理对象
return reactiveMap.get(data)
}
// 如果存在标识, 说明data被代理过了
if (data['__v_isReactive']) {
return data
}
const proxy = new Proxy(data, {
get(target, key) {
if (key == '__v_isReactive') return true // 新增
// 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
},
})
reactiveMap.set(data, proxy)
return proxy
}
/**
* 注册副作用函数
* @params [function]: 要注册的 副作用函数
*/
export function effect(fn) {
if (typeof fn !== 'function') return
// 将当前注册的副作用函数 保存 到全局变量中
activeEffect = fn
// 执行当前副作用函数, 收集依赖
fn()
// 重置全局变量
activeEffect = null
}
测试用例
<!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>
<script src="./reactive.js"></script>
</head>
<body>
<script>
const source = { name: 'hello' }
const p = reactive(source)
const p1 = reactive(p)
console.log(p === p1) // true
</script>
</body>
</html>