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 相关文章推荐
对google个性主页的拖拽效果的js的完整注释[转]
Apr 10 Javascript
使表格的标题列可左右拉伸jquery插件封装
Nov 24 Javascript
7个让JavaScript变得更好的注意事项
Jan 28 Javascript
jquery中attr和prop的区别分析
Mar 16 Javascript
JavaScript获取页面中第一个锚定文本的方法
Apr 03 Javascript
JavaScript实现的圆形浮动标签云效果实例
Aug 06 Javascript
微信小程序 获取微信OpenId详解及实例代码
Oct 31 Javascript
js原生实现FastClick事件的实例
Nov 20 Javascript
Vue.js实战之通过监听滚动事件实现动态锚点
Apr 04 Javascript
微信小程序视图template模板引用的实例详解
Sep 20 Javascript
JavaScript函数式编程(Functional Programming)组合函数(Composition)用法分析
May 22 Javascript
使用vscode快速建立vue模板过程详解
Oct 10 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实现将wav文件转换成图像文件并在页面中显示的方法
2015/04/21 PHP
smarty中改进truncate使其支持中文的方法
2016/05/30 PHP
替换php字符串中的单引号为双引号的方法
2017/02/16 PHP
Yii框架模拟组件调用注入示例
2019/11/11 PHP
juqery 学习之四 筛选查找
2010/11/30 Javascript
javascript中有趣的反柯里化深入分析
2012/12/05 Javascript
浅析JQuery获取和设置Select选项的常用方法总结
2013/07/04 Javascript
如何使用Javascript获取距今n天前的日期
2013/07/08 Javascript
JavaScript日期时间格式化函数分享
2014/05/05 Javascript
单元选择合并变色示例代码
2014/05/26 Javascript
jQuery实现动画效果circle实例
2015/08/06 Javascript
jQuery使用animate创建动画用法实例
2015/08/07 Javascript
Jquery数字上下滚动动态切换插件
2015/08/08 Javascript
基于jquery实现页面滚动到底自动加载数据的功能
2015/12/19 Javascript
Bootstrap的modal拖动效果
2016/12/25 Javascript
jquery处理checkbox(复选框)是否被选中实例代码
2017/06/12 jQuery
Vue官网todoMVC示例代码
2018/01/29 Javascript
Vue-cli@3.0 插件系统简析
2018/09/05 Javascript
教你如何编写Vue.js的单元测试的方法
2018/10/17 Javascript
如何让微信小程序页面之间的通信不再变困难
2019/06/03 Javascript
浅谈vue 组件中的setInterval方法和window的不同
2020/07/30 Javascript
Django处理文件上传File Uploads的实例
2018/05/28 Python
对Python中数组的几种使用方法总结
2018/06/28 Python
python的命名规则知识点总结
2019/10/04 Python
使用Python将图片转正方形的两种方法实例代码详解
2020/04/29 Python
对python中arange()和linspace()的区别说明
2020/05/03 Python
Python pip使用超时问题解决方案
2020/08/03 Python
HTML5的新特性(1)
2016/03/03 HTML / CSS
html5实现图片转圈的动画效果——让页面动起来
2017/10/16 HTML / CSS
英国最大的网上药品商店:Chemist Direct
2017/12/16 全球购物
美国沃尔玛网上超市:Walmart
2020/08/14 全球购物
连带责任保证书
2014/04/29 职场文书
敬老月活动总结
2014/08/28 职场文书
交通事故和解协议书
2014/09/25 职场文书
亚布力滑雪场导游词
2015/02/09 职场文书
初中班级口号霸气押韵
2015/12/24 职场文书