Vue3.X响应式对比Vue2.X优点
Vue2.X中使用Object.difineProperty做数据劫持,并且不支持数组,需要覆盖数组的7个方法,额外增加通知逻辑,对新增的属性拦截不到,需要额外的api来实现,并且在初始化时需要大量的observer,dep,watcher去做数据响应式处理,互相添加映射关系,消耗大量内存。
Vue3.X中使用Proxy代理搭配Reflect反射做响应式处理,初始化并不会做处理,只有在使用时才去做代理。
实现原理
相关api有:
- effect(fn):传⼊fn,返回的函数将是响应式的,内部代理的数据发⽣变化,它会再次执⾏
- track(target, key):建⽴响应式函数与其访问的⽬标(target)和键(key)之间的映射关系
- trigger(target, key):根据track()建⽴的映射关系,找到对应响应式函数并执⾏它
const isObj = v => typeof v === 'object' && v !== null
function reactive(obj) {
if (!isObj(obj)) {
return obj
}
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log('get', key)
//依赖收集
track(target, key)
return isObj(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log('set', key)
//触发副作用
trigger(target, key)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log('deleteProperty')
//触发副作用
trigger(target, key)
return res
}
})
}
// 临时存储副作用函数
const effectStack = []
// 副作用函数:建立传入fn和其内部的依赖之间的映射关系
function effect(fn) {
// 执行fn触发依赖的get方法
const e = createReactiveEffect(fn)
// 立即执行
e()
return e
}
function createReactiveEffect(fn) {
//封装fn:错误处理,保存到stack
const effect = function(...args) {
try {
// 入栈
effectStack.push(effect)
// 立即执行
return fn(...args)
} finally {
// 出栈
effectStack.pop()
}
}
return effect
}
// 依赖收集
const targetMap = new WeakMap()
function track(target, key) {
// 获取副作用函数
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 初始化时target这个key不存在
let depMap = targetMap.get(target)
if (!depMap) {
depMap = new Map()
targetMap.set(target, depMap)
}
// 从deoMap中获取副作用函数集合
let deps = depMap.get(key)
// 初始化时deps不存在
if (!deps) {
deps = new Set()
depMap.set(key, deps)
}
// 放入新传入的副作用函数,使用set自带去重
deps.add(effect)
}
}
// 触发副作用
function trigger(target, key) {
// 获取target,key对应的set
const depMap = targetMap.get(target)
if (!depMap) return
const deps = depMap.get(key)
if (deps) {
// 循环执行内部所有副作用函数
deps.forEach(dep => dep())
}
}
const state = reactive({ foo: 'foo', bar: { n: 1 }, arr: [1, 2, 3] })
effect(() => {
console.log('effect', state.foo)
})
effect(() => {
console.log('effect2', state.foo,state.bar.n)
})
state.foo='foooooo'