ES6 Proxy实现Vue的变化检测问题

Vue变化检测Object使用DefineProperty、数组使用方法拦截实现。最近,Vue3.0将采用ES6 Proxy的形式重新实现Vue的变化检测,在官方还没给出新方法之前,我们先实现一个基于Proxy的变化检测。

模块划分

参照之前Vue变化检测的代码,将Vue 变化检测的功能分为以下几个部分。

Observer

Dep

Watcher

Utils

首先,我们要确定的问题是,将Dep依赖搜集存在哪里。Vue 2.x里,Object的依赖收集放在defineRactive,Array的依收集存入到Observer中。ES6 Proxy里,考虑到让handler访问dep,我们将依赖放入到Observer中。

Observer

observer.js功能代码如下:

import Dep from './dep'; import { isObject } from './utils'; export default class Observer { constructor (value) { // 递归处理子元素 this.obeserve(value); // 实现当前元素的代理 this.value = this.proxyTarget(value); } proxyTarget (targetBefore, keyBefore) { const dep = new Dep(); targetBefore.__dep__ = dep; let self = this; const filtersAtrr = val => ['__dep__', '__parent__'].indexOf(val) > -1; return new Proxy(targetBefore, { get: function(target, key, receiver){ if (filtersAtrr(key)) return Reflect.get(target, key, receiver); if (!Array.isArray(target)) { dep.depend(key); } // sort/reverse等不改变数组长度的,在get里触发 if (Array.isArray(target)) { if ((key === 'sort' || key === 'reverse') && target.__parent__) { target.__parent__.__dep__.notify(keyBefore); } } return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver){ if (filtersAtrr(key)) return Reflect.set(target, key, value, receiver); // 新增元素,需要proxy const { newValue, isChanged } = self.addProxyTarget(value, target, key, self); // 设置key为新元素 Reflect.set(target, key, newValue, receiver); // notify self.depNotify(target, key, keyBefore, dep, isChanged); return true; }, }); } addProxyTarget(value, target, key, self) { let newValue = value; let isChanged = false; if (isObject(value) && !value.__parent__) { self.obeserve(newValue); newValue = self.proxyTarget(newValue, key); newValue.__parent__ = target; isChanged = true; } return { newValue, isChanged, } } depNotify(target, key, keyBefore, dep, isChanged) { if (isChanged && target.__parent__) { target.__parent__.__dep__.notify(keyBefore); return; } if (Array.isArray(target)) { if (key === 'length' && target.__parent__) { target.__parent__.__dep__.notify(keyBefore); } } else { dep.notify(key); } } obeserve(target) { // 只处理对象类型,包括数组、对象 if (!isObject(target)) return; for (let key in target) { if (isObject(target[key]) && target[key] !== null) { this.obeserve(target[key]); target[key] = this.proxyTarget(target[key], key); // 设置__parent__,方便子元素调用 target[key].__parent__ = target; } } } }

在Observer中,针对对象,只需要执行 dep.depend(key) 、 dep.notify(key) 即可。添加 key 是为了能正确的触发收集,不知道怎么说明白为什么要这样做,只能一切尽在不言中了。

Array, 如何实现依赖的收集和触发那。依赖收集与Object类似, dep.depend(key) 完成数组的收集。关于触发,可以分为两个方面,一是改变数组长度、二未改变数组长度的。改变数组长度的,在set里,通过长度属性的设置触发父级元素的notify。为什么要使用父级元素的notify那?我们可以分析以下,在你设置数组的长度时,这时候的target\key\value分别是[]\length*, 这个时候,数组的依赖收集是没有的,你watcher的是数组,并不是数组本身。这个时候只能通过 target.__parent__.__dep__.notify(keyBefore) 触发父级的收集,完成数据变化的检测。二对于未改变数组长度的,这里的做法,虽然是直接 target.__parent__.__dep__.notify(keyBefore) 触发依赖,但是有个严重的问题,实际上更新的数据不是最新的,这个地方暂时还没想到比较好的方法,欢迎大家讨论。

Dep

Dep.js

let uid = 0; export default class Dep { constructor () { this.subs = {}; this.id = uid++; } addSub(prop, sub) { this.subs[prop] = this.subs[prop] || []; this.subs[prop].push(sub); } removeSub(prop, sub) { this.remove(this.subs[prop] || [], sub); } depend(prop) { if (Dep.target) { // 传入的是当前依赖 Dep.target.addDep(prop, this) } } notify(prop) { const subs = (this.subs[prop] || []).slice(); for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } remove(arr, item) { if (arr.length) { const index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1); } } } } Dep.target = null const targetStack = [] export function pushTarget (_target) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/fc475921a5e95b0d5d22de62a81b95af.html