vue3为什么要用proxy替代defineProperty

在这之前,我们得先了解下vue的核心理念mutable

不管是vue2还是vue3,在实现的过程中,核心概念一直保持稳定,以可变数据源为核心的理念,来实现整个UI变动更新

用最简单的讲法就是:初始化数据生成了页面,直接修改源数据触发更新,页面重新渲染

关注vue的人都知道,vue3里面使用了proxy替换了defineProperty,

在使用vue2的时候,我们经常会碰到一个问题,添加新的对象属性obj.a = 1会无法被vue2劫持,必须使用vue2提供的$set方法来进行更新

这个的原因想必大家也都清楚,因为defineProperty只能对当前对象的其中一个属性进行劫持

const a = {
  b: 1,
};
Object.defineProperty(a, 'b', {
  set: function() {},
  get: function() {},
});

当我们给a对象新增一个属性的时候,当前新增的属性并没有被defineProperty劫持,虽然在对应的对象上依旧成功的生成了一个新的属性,但是我们知道,vue2是通过defineProperty的setter与getter进行数据劫持的,既然新增的数据并没有被劫持,所以无论怎么更新,页面依旧不会重新渲染

而在vue3中,使用proxy来进行数据代理就完全没有这个顾虑了

const p = new Proxy({
  a: 1,
  b: 2,
}, {
  get: function(obj, value) {
    console.log('get', obj, value);
    return Reflect.get(obj, value);
  },
  set: function(obj, prop, value) {
    console.log('set', obj, prop, value);
    return Reflect.set(obj, prop, value);
  },
})

proxy对于数据的代理,是能够响应新增的属性,当新增一个属性的时候,可以响应到get中,对当前对象进行代理

vue3是如何通过proxy代理的

首先可以看下vue3新增的几个主要apiref, reactive, effect,computed

ref和reactive
const normal = ref(0);
const state = reactive({
  a: 1,
  b: 2,
})

vue3中对vue2的兼容处理也是使用了reactive,即instance.data = reactive(data),将整个data属性使用reactive进行代理

我们知道,vue2中的data就是使用Object.definePerproty进行数据劫持的, 那么在reactive中,他是如何使用proxy进行数据代理的,来兼容老的书写方式与新的compositionApi

ps: 由于在reactive里面也只是通过proxy对传入的数据校验和代理,最主要的还是set和get,所以我们还是直接上垒吧,毕竟心急吃得了热豆腐

get

可以分析一下,vue2也好,vue3也罢,针对于数据的获取所做的事情主要内容不会有什么区别

  1. 获取当前需要的key的数据
  2. 依赖采集

但是,针对于vue3使用proxy的特性,在这边额外做了一部分的兼容

  1. 如果获取的数据是一个对象,则会对对象再使用reactive进行一次数据的代理
  2. 如果是shallow类型的数据代理, 则直接返回当前获取到的数据

effect依赖采集

vue除去正常的data的数据代理以外,还有对应的computed和watch,而在vue3中直接使用了watchEffect和computed方法能够直接生成对应的内容

他们的数据更新和依赖处理都是依托于当前data数据上的get进行依赖的收集

掐头去尾的来看最核心的代码

// targetMap当前所有代理的数据的一个Map集合
// depsMap当前代理的数据的每一个Key所对应的Map集合
// dep当前代理的数据中的key的对应依赖
// activeEffect当前由effect或者computed生成的数据

let depsMap = targetMap.get(target)
if (!depsMap) {
  targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
  depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
  dep.add(activeEffect)
  activeEffect.deps.push(dep)
}

单纯从这段代码出发去解读的话,可能会有一定的困难,换个角度,从vue3的整体使用情况出发,返回来解读这段代码

setup() {
  const b = reactive({
    c: 1,
    b: 2,
  });
  // effect是vue中的reactivity包直接返回出来的方法
  const a = effect(() => {
    return b.c;
  })
}

首先,在effect中使用了前面通过reactive定义的b,从表面现象出发的话,我们能知道,当b.c发生变化的时候,a也会同步发生变化

这个变化的原因就是上述源码中的activeEffect,当创建的effect被调用的时候,会将activeEffect设置为自身,并执行相应的回调函数,函数的调用会触发到各自使用到的数据的getter,将对应的effect依赖注入到每个使用的数据上

至于为什么会设置这么复杂的一个属性的依赖获取,是因为使用proxy的原因,proxy代理了一整个对象,就不能像vue2使用Obect.defineProperty直接在getter里面就当前的字段进行一个依赖绑定,所以在vue3中是直接将整个对象作为一个Map,每个Map的key都是对应的属性,而value则是所有依赖当前属性的对象

set

同get,依旧保持着原先的思路跟模式

  1. 设置当前数据
  2. 发布已订阅的数据(触发依赖更新)

在vue3中,还是有部分区别的,毕竟是单独拉出去的一个库

  1. 如果直接调用effect,当检测的数据发生变化的时候会直接修改
  2. 如果调用watch或者watchEffect,则会走vue自身的调度方案

所以,如果想当前的数据直接可以更新的话, 可以优先使用effect,他会比watchEffect的更新速度快一点,劣势是可能很多东西得自己写 =,=

至于怎么实现的其实就很简单了

  1. 获取当前更新的数据的受依赖项
  2. 分组进入等待运行的Set中
  3. 执行

但是里面有一个特殊的处理,针对于数组的length属性,这个属性是有一定区别的,接下来具体讲讲在vue3中的数组操作

数组

在vue2中的,针对数组是多做了一层处理,代理了数组的基本方法,这是因为使用Object.defineProperty在数组上面天然存在劣势

具体原因在vue的文档中写的非常清楚了,这里就不详细叙述了

文档地址

而在vue3中使用proxy就完美的解决了这个问题,只是因为proxy能够监听数组的变化,做个测试

const a = new Proxy([1,2], {
  get: function(obj, prop) {
    console.log('get', obj, prop);
    return Reflect.get(obj, prop);
  },
  set: function(obj, prop, value) {
    console.log('set', obj, prop, value);
    return Reflect.set(obj, prop, value);
  },
});
a.push(1);

get [1,2] push
get [1,2] length
set [1,2] 2 1
set [1,2, 1] length 3

当我们代理了一个数组之后,直接调用push插入一个新的数据,能够明显的看到getter跟setter都会被调用两次,一次是调用的push方法,而另一次是数组的长度length,也就是说,proxy不仅仅会检测到我们当前调用的方法,还能够知道我们的数据长度是否发生了变化

看到这边,可能会有一个疑惑,push是对当前数组进行的操作,但是数组里面还有部分方法是会返回一个新的数组,proxy是否会对新生成的数组也进行代理,这里我们拿splice举个例子

// a= [1,2]
a.splice(0, 1)

get [1,2] push
get [1,2] length
get [1,2] constructor
get [1,2] 0
get [1,2] 1
set [1,2] 0 2
set [2,empty] length 1

从表现形式来看,proxy代理之后的数组只会对当前数组的内容进行监听,也就是调用splice之后新生成的数组的变化是不会被代理的

现在我们回过头来看下vue3的trigger方法,这个是vue在set完成之后触发的依赖更新,同样的掐个头去个尾,除去正常的执行以外,我们看下针对数组做的优化

// add方法是将当前的依赖项添加进一个等待更新的数组中
else if (key === 'length' && isArray(target)) {
  depsMap.forEach((dep, key) => {
   if (key === 'length' || key >= (newValue as number)) {
    add(dep)
   }
  })
}

由于我们知道, 在一次操作数组的时候会进行多次的set,那么如果每次set都要去更新依赖的话,会造成性能上的浪费,所以在vue3里面只有在set length的时候才会去调用add方法,然后统一执行所有的更新

结语

不得不说,proxy比defineProperty强大了太多,不仅解决了vue的历史难题,让vue的体验更上了一层,更是去除了不少因为defineProperty而必须要的方法,精简了vue的包大小

虽然proxy的兼容性是比defineProperty差不少,但是在vue里面基本已经抛弃了IE,所以如果你的项目需要在ie下运行的话,那就请放弃vue这个选择,使用低版本的react的吧,哈哈哈哈哈哈

在移动端里面基本上就是没有这种版本的限制,实在是版本低不能使用proxy的话,相信去找找polyfill是能够找到的

到此这篇关于vue3为什么要用proxy替代defineProperty的文章就介绍到这了,更多相关vue3 proxy替代defineProperty内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 为什么Vue3.0使用Proxy实现数据监听(defineProperty表示不背这个锅)

    导 读 vue3.0中,响应式数据部分弃用了 Object.defineProperty ,使用 Proxy 来代替它.本文将主要通过以下方面来分析为什么vue选择弃用 Object.defineProperty . Object.defineProperty 真的无法监测数组下标的变化吗? 分析vue2.x中对数组 Observe 部分源码 对比 Object.defineProperty 和 Proxy 一.无法监控到数组下标的变化? 在一些技术博客上看到过这样一种说法,认为 Object.

  • vue3为什么要用proxy替代defineProperty

    在这之前,我们得先了解下vue的核心理念mutable 不管是vue2还是vue3,在实现的过程中,核心概念一直保持稳定,以可变数据源为核心的理念,来实现整个UI变动更新 用最简单的讲法就是:初始化数据生成了页面,直接修改源数据触发更新,页面重新渲染 关注vue的人都知道,vue3里面使用了proxy替换了defineProperty, 在使用vue2的时候,我们经常会碰到一个问题,添加新的对象属性obj.a = 1会无法被vue2劫持,必须使用vue2提供的$set方法来进行更新 这个的原因想

  • 浅谈vue2的$refs在vue3组合式API中的替代方法

    如果你有过vue2的项目开发经验,那么对$refs就很熟悉了.由于vue3的断崖式的升级,在vue3中如何使用$refs呢?想必有遇到过类似的问题,我也有一样的疑惑.通过搜索引擎和github,基本掌握如何使用$refs.在vue3中使用组合式API的函数ref来代替静态或者动态html元素的应用. 最近业余在学习vue3项目<蜡笔(Crayon)管理模板:Vue3 + Vuex4 + Ant Design2>开发,这两天迭代推进了一点,实现了chart图表组件,写文章的时候发现提交代码的co

  • vue3中如何获取proxy包裹的数据

    目录 如何获取proxy包裹的数据 具体问题 解决办法 vue3 proxy基本用法 新的改变 基本使用 如何获取proxy包裹的数据 在进行 vue3+ts+elementplus 重构vue2项目时遇到了关于proxy的问题 具体问题 使用el-upload组件进行图片上传,然后绑定handleChange方法进行图片改变的监听,将上传的图片push到fileList数组中. const handleChange: UploadProps['onChange'] = (file, fileL

  • Vue3 的响应式和以前有什么区别,Proxy 无敌?

    前言 大家都知道,Vue2 里的响应式其实有点像是一个半完全体,对于对象上新增的属性无能为力,对于数组则需要拦截它的原型方法来实现响应式. 举个例子: let vm = new Vue({ data() { return { a: 1 } } }) // ❌ oops,没反应! vm.b = 2 let vm = new Vue({ data() { return { a: 1 } }, watch: { b() { console.log('change !!') } } }) // ❌ oo

  • Vue3使用Proxy实现数据监听的原因分析

    vue 数据双向绑定原理,而这个方法有缺点,并且不能实现数组和对象的部分监听情况;具体也可以看我之前写的一篇博客: 关于 Vue 不能 watch 数组 和 对象变化的解决方案,最新的 Proxy,相比 vue2 的 Object.defineProperty,能达到速度加倍.内存减半的成效.具体是怎么实现.以及对比旧的实现方法为啥能有速度加倍.内存减半的特性,下面我们来聊聊: Vue 初始化过程 Vue 的初始化过程,分别有Observer.Compiler和Watcher,当我们 new V

  • Vue3对比Vue2的优点总结

    1.为什么要有vue3 我们使用vue2常常会遇到一些体验不太好的地方,比如: 随着功能的增长,需求的增加,复杂组件的代码越来越难以维护,逻辑混乱,虽然vue2也有一些复用的方法,但是都存在一定的弊端,比如我们常常用的Mixin,特别容易发生命名冲突,暴露出来的变量意图不是很明显,重用到其他组件容易冲突. vue2对于typeScript的支持非常有限,没有考虑到ts的集成. vue3的出现就是为了解决vue2的弊端,其composition API很好的解决了逻辑复用的问题,而且vue3源码就

  • 如何使用proxy实现一个简单完整的MVVM库的示例代码

    前言 MVVM 是当前时代前端日常业务开发中的必备模式(相关框架如react,vue,angular 等), 使用 MVVM 可以将开发者的精力更专注于业务上的逻辑,而不需要关心如何操作 dom.虽然现在都 9012 年了,mvvm 相关原理的介绍已经烂大街了,但出于学习基础知识的目的(使用 proxy 实现的 vue3.0 还在开发中), 在参考了之前 vue.js 的整体思路之后,自己动手实现了一个简易的通过 proxy 实现的 mvvm. 本项目代码已经开源在github,项目正在持续完善

  • Vue3 中的数据侦测的实现

    在10月05日凌晨Vue3的源代码正式发布了,来自官方的消息: 目前的版本是 Pre-Alpha , 仓库地址:Vue-next,可以通过Composition API了解更多新版本的信息,目前版本单元测试相关情况vue-next-coverage. 文章大纲: Vue 的核心之一就是响应式系统,通过侦测数据的变化,来驱动更新视图. 实现可响应对象的方式 通过可响应对象,实现对数据的侦测,从而告知外界数据变化.实现可响应对象的方式: getter 和 setter defineProperty

  • vue使用Proxy实现双向绑定的方法示例

    前言:vue3.0要用Proxy来实现双向绑定,因此先来尝试一下实现方法. 1 Object.defineProperty 实现 原来vue2的实现使用Object.defineProperty,监听set,但对于数组直接下标给数组设置值监听不了. function observe(data) { if (!data || typeof data !== 'object') { return; } // 取出所有属性遍历 Object.keys(data).forEach(function(ke

  • vue proxy 的优势与使用场景实现

    1.前言 随着 vue3.x 的消息越来越多, proxy 的讨论也.相对于 Object.defineProperty , proxy 有什么区别,有什么优势,以及可以应用在什么地方.该文章就简单的介绍下 2.Object.defineProperty 讲 proxy 之前,先回顾下 Object.defineProperty .大家都知道, vue2.x 以及之前的版本是使用 Object.defineProperty 实现数据的双向绑定的,至于是怎样绑定的呢?下面简单实现一下 functi

随机推荐