侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

前端  /  管理员 发布于 5年前   295

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 = nullconst 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.hasOwnPropertyexport 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: lijincai20: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: resetName20:17:44.725 index.js?afc7:24 3: password newValue: hello world password , oldValue: resetPassword20: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 home20:17:44.726 index.js?afc7:36 6: addProp newValue: hello addProp , oldValue: undefined20:17:44.727 index.js?afc7:36 7: addProp newValue: Proxy {name: "ceshi", __dep__: Dep, __parent__: {…}} , oldValue: hello addProp20: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的变化检测问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

您可能感兴趣的文章:

  • vue中遇到的坑之变化检测问题(数组相关)
  • 详解ES6中的代理模式――Proxy
  • 实例解析ES6 Proxy使用场景介绍
  • 浅谈es6语法 (Proxy和Reflect的对比)
  • ES6中Proxy与Reflect实现重载(overload)的方法
  • 详细探究ES6之Proxy代理


  • 上一条:
    vue2 中二级路由高亮问题及配置方法
    下一条:
    jQuery实现Ajax功能分析【与Flask后台交互】
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客