最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

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

来源:动视网 责编:小采 时间:2020-11-27 21:55:25
文档

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

ES6 Proxy实现Vue的变化检测问题:Vue变化检测Object使用DefineProperty、数组使用方法拦截实现。最近,Vue3.0将采用ES6 Proxy的形式重新实现Vue的变化检测,在官方还没给出新方法之前,我们先实现一个基于Proxy的变化检测。 模块划分 参照之前Vue变化检测的代码,将Vue 变化检测的功能
推荐度:
导读ES6 Proxy实现Vue的变化检测问题:Vue变化检测Object使用DefineProperty、数组使用方法拦截实现。最近,Vue3.0将采用ES6 Proxy的形式重新实现Vue的变化检测,在官方还没给出新方法之前,我们先实现一个基于Proxy的变化检测。 模块划分 参照之前Vue变化检测的代码,将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()
    }

    dep 添加prop实现类型的绑定,为什么要这么做那?使用proxy代理后,你假如wahcter对象下的几个元素,此时的deps将同时存在这几个元素,你触发依赖的时候,这些依赖都会执行。因此,通过key值绑定观察事件,触发时,能完成对象的正确触发。

    watcher、utils

    import { parsePath } from './utils';
    import { pushTarget, popTarget } from './dep'
    export default class Watcher {
     constructor(vm, expOrFn, cb) {
     // dep id集合
     this.depIds = new Set();
     this.vm = vm;
     this.getter = parsePath(expOrFn);
     this.cb = cb;
     this.value = this.get();
     }
     get () {
     pushTarget(this);
     let value = this.getter.call(this.vm, this.vm);
     popTarget();
     return value;
     }
     update() {
     const oldValue = this.value;
     this.value = this.get();
     this.cb.call(this.vm, this.value, oldValue);
     }
     addDep (prop, dep) {
     const id = dep.id;
     if (!this.depIds.has(id)) {
     this.depIds.add(id);
     dep.addSub(prop, this);
     }
     }
    }

    utils.js

    /**
     * 解析简单路径
     */
    const bailRE = /[^\w.$]/;
    export function parsePath (path) {
     if (bailRE.test(path)) {
     return;
     }
     const segments = path.split('.');
     return function (obj) {
     for (let i = 0; i < segments.length; i++) {
     if (!obj) return;
     obj = obj[segments[i]];
     }
     return obj;
     };
    }
    /**
     * Define a property.
     */
    export function def (obj, key, val, enumerable) {
     Object.defineProperty(obj, key, {
     value: val,
     enumerable: !!enumerable,
     writable: true,
     configurable: true
     })
    }
    /**
     * Quick object check - this is primarily used to tell
     * Objects from primitive values when we know the value
     * is a JSON-compliant type.
     */
    export function isObject (obj) {
     return obj !== null && typeof obj === 'object'
    }
    /**
     * Check whether an object has the property.
     */
    const hasOwnProperty = Object.prototype.hasOwnProperty
    export function hasOwn (obj, key) {
     return hasOwnProperty.call(obj, key)
    }

    Utils.js/Watchers.js与Vue 2.x类似,这里就不多介绍了。

    测试一下

    test.js

    import Observer from './observer';
    import Watcher from './watcher';
    let data = {
     name: 'lijincai',
     password: '***********',
     address: {
     home: '安徽亳州谯城区',
     },
     list: [{
     name: 'lijincai',
     password: 'you know it Object',
     }], 
    };
    const newData = new Observer(data);
    let index = 0;
    const watcherName = new Watcher(newData, 'value.name', (newValue, oldValue) => {
     console.log(`${index++}: name newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherPassword = new Watcher(newData, 'value.password', (newValue, oldValue) => {
     console.log(`${index++}: password newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherAddress = new Watcher(newData, 'value.address', (newValue, oldValue) => {
     console.log(`${index++}: address newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherAddressHome = new Watcher(newData, 'value.address.home', (newValue, oldValue) => {
     console.log(`${index++}: address.home newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherAddProp = new Watcher(newData, 'value.addProp', (newValue, oldValue) => {
     console.log(`${index++}: addProp newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherDataObject = new Watcher(newData, 'value.list', (newValue, oldValue) => {
     console.log(`${index++}: newValue:`, newValue, ', oldValue:', oldValue);
    });
    newData.value.name = 'resetName';
    newData.value.password = 'resetPassword';
    newData.value.name = 'hello world name';
    newData.value.password = 'hello world password';
    newData.value.address.home = 'hello home';
    newData.value.address.home = 'hello home2';
    newData.value.addProp = 'hello addProp';
    newData.value.addProp ={
     name: 'ceshi',
    };
    newData.value.addProp.name = 'ceshi2';
    newData.value.list.push('1');
    newData.value.list.splice(0, 1);
    newData.value.list.sort();
    newData.value.list.reverse();
    newData.value.list.push('1');
    newData.value.list.unshift({name: 'nihao'});
    newData.value.list[0] = {
     name: 'lijincai',
     password: 'you know it Array',
    };
    newData.value.list[0].name = 'you know it array after';
    newData.value.list.pop();
    newData.value.list.shift();
    newData.value.list.length = 1;

    我们使用对象、数组测试一下我们的ES6 Proxy检测。

    20:17:44.725 index.js?afc7:20 0: name newValue: resetName , oldValue: lijincai
    20:17:44.725 index.js?afc7:24 1: password newValue: resetPassword , oldValue: ***********
    20:17:44.725 index.js?afc7:20 2: name newValue: hello world name , oldValue: resetName
    20:17:44.725 index.js?afc7:24 3: password newValue: hello world password , oldValue: resetPassword
    20:17:44.726 index.js?afc7:32 4: address.home newValue: hello home , oldValue: 安徽亳州谯城区
    20:17:44.726 index.js?afc7:32 5: address.home newValue: hello home2 , oldValue: hello home
    20:17:44.726 index.js?afc7:36 6: addProp newValue: hello addProp , oldValue: undefined
    20:17:44.727 index.js?afc7:36 7: addProp newValue: Proxy {name: "ceshi", __dep__: Dep, __parent__: {…}} , oldValue: hello addProp
    20:17:44.727 index.js?afc7:40 0: newValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.728 index.js?afc7:40 1: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.729 index.js?afc7:40 2: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.731 index.js?afc7:40 3: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.734 index.js?afc7:40 4: newValue: Proxy {0: "1", 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", 1: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.735 index.js?afc7:40 5: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.735 index.js?afc7:40 6: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.736 index.js?afc7:40 7: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.737 index.js?afc7:40 8: newValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.738 index.js?afc7:40 9: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.738 index.js?afc7:40 10: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}

    我们看到了ES6 Proxy后实现了Object/Array的检测,虽然还存在一些问题,但是基本的侦测变化的功能都已经具备了。

    总结

    以上所述是小编给大家介绍的ES6 Proxy实现Vue的变化检测问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
    如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

    文档

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

    ES6 Proxy实现Vue的变化检测问题:Vue变化检测Object使用DefineProperty、数组使用方法拦截实现。最近,Vue3.0将采用ES6 Proxy的形式重新实现Vue的变化检测,在官方还没给出新方法之前,我们先实现一个基于Proxy的变化检测。 模块划分 参照之前Vue变化检测的代码,将Vue 变化检测的功能
    推荐度:
    标签: VUE 检测 的问题
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top