Vue2 响应式
响应式主要只包含三个元素:Observer、Dep、Watcher
Observer:递归地监听对象上的所有属性,在属性值改变的时候,触发相应的watcherWatcher:观察者,当监听的数据值修改时,执行响应的回调函数(Vue里面的更新模板内容)Dep:连接Observer和Watcher的桥梁,每一个Observer对应一个Dep,它内部维护一个数组,保存与该Observer相关的Watcher
class Observer {
constructor(target, key, val) {
this.dep = new Dep()
if (Object.prototype.toString.call(val).slice(8, -1) === 'object') {
for (let key in val) {
if (!val.hasOwnProperty(key)) continue
new Observer(val, key, val[key])
}
}
if (Array.isArray(val)) {
// 数组方法拦截操作...
for (let [key, val2] of val) new Observer(val, key, val2)
return
}
this.defineReactive(target, key, val)
}
defineReactive(target, key, val) {
const self = this
Object.defineProperty(target, key, {
enumerable: true,
configurable: false,
get() {
Dep.target && self.dep.addSub(Dep.target)
return val
},
set(newVal) {
if (newVal === val) return
self.dep.notify()
val = newVal
},
})
}
}
class Dep {
constructor() {
this.deps = []
}
addSub(dep) {
if (this.deps.includes(dep)) return
this.deps.push(dep)
}
notify() {
this.deps.forEach(dep => dep.update())
}
}
class Watcher {
constructor(renderFn) {
this.renderFn = renderFn
}
update() {
Dep.target = this
this.renderFn()
Dep.target = null
}
}
官方原理图如下
// 测试
let app = document.getElementById('app')
let vm = {
data: {
id: 1007,
goods: { price:1 }
}
}
let ob = new Observer(vm, 'data', vm.data)
let watcher = new Watcher(() => app.innerHTML = vm.data.goods.price)
watcher.update()
let btn = document.getElementById('btn')
btn.onclick = () => vm.data.goods.price++
对照测试示例如下
Vue3 响应式
主要由三个部分构成reactive、track、trigger
使用Proxy和Reflect,Proxy 机制相对于 Object.defineProperty 在性能上更优,因为 Proxy 可以直接代理整个对象,而不需要遍历属性并逐一添加 getter 和 setter
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, val, receiver) {
if (target[key] === val) return val
trigger(target, key)
return Reflect.set(target, key, val, receiver)
},
})
}
const targetMap = new WeakMap()
function track(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect) // activeEffect是副作用函数,用于追踪更新视图,由Vue3响应式系统自动处理
}
function trigger(target, key) {
const dep = targetMap.get(target).get(key)
dep.forEach(effect => effect())
}