自vue3.0发布以来也好几个月了,最近也一直在忙碌手头上的工作,没有时间来学习vue3.0。刚好快要过年了,公司的事情不是很多,这才抽出时间来学习下vue3.0的响应式原理!
回顾Vue2.x 响应式原理
- 利用观察者模式 + Object.defineProperty ,收集依赖效率较低,对于深层次数据收集不友好
- 对于复杂类型数据的删除等操作,监听操作实现较为麻烦
Vue3.0响应式原理
- 使用了Proxy,收集依赖更加的高效,对于深层次数据的收集更加的方便
- 可以更好的监听数据的新增、删除等操作
Vue3.0响应式原理图
代码实现
//判断是否是对象
function isObject(obj){
return typeof obj === 'object' && obj !== null;
}
//创建代理对象
function reactive(target){
if(!isObject(target)) return target;
//创建 代理对象 监听规则
let handler = {
get(target,key,reciver){
//拦截js对象的获取操作
let res = Reflect.get(...arguments);
//收集依赖
//将effect 和 key 关联起来 不断触发
track(target,key);
//如果访问的key 返回是一个对象 递归继续获取
return isObject(res)? reactive(res) : res;
},
set(target,key,value,reciver){
let res = Reflect.set(...arguments);
//触发更新
trigger(target,key);
return res;
},
deleteProperty(target, key) {
let res = Reflect.deleteProperty(target, key);
return res;
},
}
//返回代理对象
return new Proxy(target,handler);
}
// 创建栈 存储响应式函数
let effectStrack = []
//创建 wekmap 将 响应式函数 和 key 关联 WeakMap( target : Map( key : Set( effectFn ) ) ) 基本结构
let targetMap = new WeakMap();
// 创建响应式函数
function effect(cb){
const effectFn = () => {
try{
//将响应式函数压入栈
effectStrack.push(effectFn);
//执行 cb 触发 代理对象的gettter
return cb();
}finally{
//执行完毕后 出栈销毁
effectStrack.pop();
}
}
//第一次进入 执行一次 一遍 能和 key 建议映射关系
effectFn();
return effectFn;
}
//收集依赖
function track(target,key){
//判断栈中是否有响应式函数
let effectFn = effectStrack[effectStrack.length - 1];
if(effectFn){
//从映射中取出 代理对象的value 如果不存在,建立 关系
let depsMap = targetMap.get(target); //wekmap map 是键值对 结构 但是他的key 可以值对象等类型
if(!depsMap){
depsMap = new Map();
targetMap.set(targetMap,depsMap);
//此时 数据结构为 WeakMap( target : Map( ) ) map 是键值对 结构
//再次获取 wekmap的 key 为target对象的value 是否存在 key这个键 如不存在 继续创建
let deps = depsMap.get(key);
if(!deps){
deps = new Set();
depsMap.set(key,deps);
//判断 set中是否 有响应函数 没有 继续创建 set 是 值结构
if( !deps.has(effectFn) ){
deps.add(effectFn);
//到这里结构建立完毕
//WeakMap( target : Map( key : Set( effectFn ) ) )
}
}
}
}
}
//触发依赖
function trigger(target,key){
//直接获取set集合
let depsMap = targetMap.get(targetMap);
if(depsMap){
let deps = depsMap.get(key)
deps.forEach( effect => {
effect();
} )
}
}
let obj = reactive({
name:'张三'
})
effect( () => {
document.body.innerHTML = obj.name;
})
setInterval(() => {
obj.name = '我也曾经是一个单纯、善良的少年...'+Math.random();
},1000)
###补充
这里简单实现以下,还有许多漏铜,比如说 还需要判断 一个对象被代理多次 等问题,欢迎补充。