vue3为什么要用proxy替代defineProperty


Posted in Javascript onOctober 19, 2020

在这之前,我们得先了解下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内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
超级强大的表单验证
Jun 26 Javascript
用js+xml自动生成表格的东西
Dec 21 Javascript
jquery ajax提交表单数据的两种方式
Nov 24 Javascript
JQuery Easyui Tree的oncheck事件实现代码
May 28 Javascript
jQuery图片轮播滚动切换代码分享
Apr 20 Javascript
JS实现仿腾讯微博无刷新删除微博效果代码
Oct 16 Javascript
将html页面保存成图片,图片写入pdf的实现方法(推荐)
Sep 17 Javascript
jQuery 插件封装的方法
Nov 16 Javascript
jQuery实现发送验证码并60秒倒计时功能
Nov 25 Javascript
jQuery 利用ztree实现树形表格的实例代码
Sep 27 jQuery
vue单页开发父子组件传值思路详解
May 18 Javascript
详解Vue调用手机相机和相册以及上传
May 05 Javascript
jQuery实现推拉门效果
Oct 19 #jQuery
小程序实现左滑删除的效果的实例代码
Oct 19 #Javascript
jQuery实现图片切换效果
Oct 19 #jQuery
jQuery实现回到顶部效果
Oct 19 #jQuery
jQuery实现放大镜案例
Oct 19 #jQuery
vue v-model的用法解析
Oct 19 #Javascript
jQuery插件实现图片轮播效果
Oct 19 #jQuery
You might like
解析PHP实现下载文件的两种方法
2013/07/05 PHP
写一段简单的PHP建立文件夹代码
2015/01/06 PHP
php对象和数组相互转换的方法
2015/05/12 PHP
PHP实现百度人脸识别
2019/05/06 PHP
修改发贴的编辑功能
2007/03/07 Javascript
JS对URL字符串进行编码/解码分析
2008/10/25 Javascript
jquery下拉select控件操作方法分享(jquery操作select)
2014/03/25 Javascript
js动态创建及移除div的方法
2015/06/03 Javascript
编写高性能Javascript代码的N条建议
2015/10/12 Javascript
js+canvas绘制矩形的方法
2016/01/28 Javascript
AngularJS基础 ng-submit 指令简单示例
2016/08/03 Javascript
老生常谈的跨域处理
2017/01/11 Javascript
jquery使用EasyUI Tree异步加载JSON数据(生成树)
2017/02/11 Javascript
JS实现向iframe中表单传值的方法
2017/03/24 Javascript
如何使用CSS3和JQuery easing 插件制作绚丽菜单
2019/06/18 jQuery
Layui动态生成select下拉选择框不显示的解决方法
2019/09/24 Javascript
如何使用three.js 制作一个三维的推箱子游戏
2020/07/29 Javascript
Vue中用JSON实现刷新界面不影响倒计时
2020/10/26 Javascript
[01:23:24]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Elephant BO3 第三场 2月7日
2021/03/11 DOTA
Python列表计数及插入实例
2014/12/17 Python
详谈Python高阶函数与函数装饰器(推荐)
2017/09/30 Python
Python脚本按照当前日期创建多级目录
2019/03/01 Python
python TK库简单应用(实时显示子进程输出)
2019/10/29 Python
Python内置数据类型list各方法的性能测试过程解析
2020/01/07 Python
使用python的turtle函数绘制一个滑稽表情
2020/02/28 Python
在Python中用GDAL实现矢量对栅格的切割实例
2020/03/11 Python
python解压zip包中文乱码解决方法
2020/11/27 Python
使用css3和jquery实现可伸缩搜索框
2014/02/12 HTML / CSS
AutoShack.com加拿大:北美主要的汽车零部件零售商
2019/07/24 全球购物
美体小铺印度官网:The Body Shop印度
2019/10/17 全球购物
Talbots官网:美国成熟女装品牌
2019/11/15 全球购物
函授教育个人学习的自我评价
2013/12/31 职场文书
公务员政审个人鉴定
2014/02/25 职场文书
经典广告词大全
2014/03/14 职场文书
辩论赛开场白大全(主持人+辩手)
2015/05/29 职场文书
教你利用Selenium+python自动化来解决pip使用异常
2021/05/20 Python