Vue3.0 响应式由 Object.defineProperty 改为 Proxy 实现
因为前者无奈监听到对象上增删属性的变动
Vue3.0的响应式用到了Proxy和Reflect两个ES6新增的性能
Proxy
Proxy 能够了解成,在指标对象之前架设一层“拦挡”,外界对该对象的拜访,都必须先通过这层拦挡,因而提供了一种机制,能够对外界的拜访进行过滤和改写。
Reflect
Reflect
对象与Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect
对象的设计目标有这样几个。
- 将
Object
对象的一些显著属于语言外部的办法(比方Object.defineProperty
),放到Reflect
对象上。 - 批改某些
Object
办法的返回后果,让其变得更正当。比方,Object.defineProperty(obj, name, desc)
在无奈定义属性时,会抛出一个谬误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
- Reflect.get(target, name, receiver)
Reflect.get
办法查找并返回target
对象的name
属性,如果没有该属性,则返回undefined
。 - Reflect.set(target, name, value, receiver)
Reflect.set
办法设置target
对象的name
属性等于value
。
createReactive
函数中改为应用Proxy
let createReactive = (target, prop, value) => { target._dep = new Dep() return new Proxy(target, { get(target, prop) { target._dep.depend() return Reflect.get(target, prop) }, set(target, prop, value) { target._dep.notify() return Reflect.set(target, prop, value) } }) }
示例:
- 间接监听对象新增的属性
- 去掉push办法的解决,间接应用
都能够胜利
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <button id="add">add</button> <div id="app"></div> <hr> <button id="addArr">addArr</button> <div id="appArr"></div></body><script> let active let effect = (fn, options = {}) => { // 为什么要减少一个_effect函数 // 因为须要给_effect减少属性 // 也能够间接给fn减少,然而因为援用类型的起因,会对fn函数造成净化 let _effect = (...args) => { try { active = _effect return fn(...args) } finally { active = null } } _effect.options = options _effect.deps = [] // effect和dep的关系-1 return _effect } let cleanUpEffect = (effect) => { // 革除依赖 // 须要反向查找effect被哪些dep依赖了 // 在effect上增加[] 建设双向索引 const { deps } = effect console.log(deps) console.log(effect) if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } } } let watchEffect = (cb) => { /* active = cb active() active = null */ let runner = effect(cb) runner() return () => { cleanUpEffect(runner) } } let nextTick = (cb) => Promise.resolve().then(cb) // 队列 let queue = [] // 增加队列 let queueJob = (job) => { if (!queue.includes(job)) { queue.push(job) // 增加之后,将执行放到异步工作中 nextTick(flushJob) } } // 执行队列 let flushJob = () => { while (queue.length > 0) { let job = queue.shift() job && job() } } let Dep = class { constructor() { // 寄存收集的active this.deps = new Set() } // 依赖收集 depend() { if (active) { this.deps.add(active) active.deps.push(this.deps) // effect和dep的关系-2 } } // 触发 notify() { this.deps.forEach(dep => queueJob(dep)) this.deps.forEach(dep => { dep.options && dep.options.schedular && dep.options.schedular() }) } } let createReactive = (target, prop, value) => { target._dep = new Dep() return new Proxy(target, { get(target, prop) { target._dep.depend() return Reflect.get(target, prop) }, set(target, prop, value) { target._dep.notify() return Reflect.set(target, prop, value) } }) } let ref = (initValue) => createReactive({}, 'value', initValue) const set = (target, prop, initValue) => createReactive(target, prop, initValue) let computed = (fn) => { let value let dirty = true // 为true表明依赖的变量产生了变动,此时须要从新计算 let runner = effect(fn, { schedular() { if (!dirty) { dirty = true } } }) return { get value() { if (dirty) { // 何时将dirty重置为true,当执行fn后 // 因而须要通过配置回调函数,在执行fn后将dirty重置为true // value = fn() value = runner() dirty = false } return value } } } let watch = (source, cb, options = {}) => { const { immediate } = options const getter = () => { return source() } // 将函数增加到count的依赖下来,当count变动时 let oldValue const runner = effect(getter, { schedular: () => applyCb() }) const applyCb = () => { let newValue = runner() if (newValue !== oldValue) { cb(newValue, oldValue) oldValue = newValue } } if (immediate) { applyCb() } else { oldValue = runner() } } // set示例: let count = ref(0) // count.v新增属性 document.getElementById('add').addEventListener('click', function () { if (!count.v) { count.v = 0 } count.v++ }) let str let stop = watchEffect(() => { str = `hello ${count.v}` document.getElementById('app').innerText = str }) // 数组push示例: let arrValue = 0 // set函数中曾经对依赖进行了一次增加 let countArr = set([], 1, 0) document.getElementById('addArr').addEventListener('click', function () { arrValue++ countArr.push(arrValue) }) watchEffect(() => { str = `hello ${countArr.join(',')}` document.getElementById('appArr').innerText = str })</script></html>