Vue3-03-Vue2 响应式 VS Vue3 响应式

本文来讲解从 Vue2 到 Vue3 响应式底层的一些改变。

前言

Vue 2.x 为什么不监听数组下标索引值的变化?

参考了很多博主的推文,自己也尝试了一下,Object.defineProperty 是可以做到监听数组的索引值的变化的,来做 getter 和 setter。

但 Vue2.x 为什么没有这么做呢?官方回答是因为性能的问题,所以不监听数组索引值的变化。

一、Vue2 响应式

1. 对象响应性

var vm = new Vue({
  data: {
    a: 1
  }
});

// vm.a 是响应式的
vm.a = 1;

// vm.b 是非响应式的
vm.b = 2;

为什么会出现这种情况?vm 是一个 Vue 的实例,它带有了 data 对象中的所有的属性,如果我们访问 vm.a,代表我们访问的是 data 数据对象中的 a 属性,

数据对象中的属性都会被转化为 getter 和 setter,vm.a = 2 会触发对应的 setter。vm.b = 2 就是在 vm 对象上添加了一个属性,所以它不会有响应性。

这里就是 Vue2 对对象处理比较薄弱的一块,我们没办法对对象新增的属性进行实时的响应。

2. 数组响应性

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
});

vm.items[1] = 'x'; // 不是响应性的

vm.items[1].length = 2; // 不是响应性的

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此,我们还是有办法来回避这些限制并保证它们的响应性。

这里来介绍一个术语:代理原型。

Vue3-03-Vue2 响应式 VS Vue3 响应式_第1张图片

假设我们有一个数组,想要监听这个数组元素的变化,我们首先要找到能够改变数组成员的方法,比如 push、pop 等方法,这些方法都能够改变数组结构。这些方法都是定义在 Array 这个数组的原型对象上,Array 是 JS 内置的一个构造函数。Array 对象的这些方法的代码我们没法去修改,所以这时我们就要想一个办法,我们能不能在要监听的数组和 Array 原型对象间嫁接一层,这个嫁接的一层就是我们所说的代理原型对象(即上图中粉色框)。

原本我们使用 push 方法时,指向的是数组原型对象上的 push 方法。现在我们做了一层嫁接,被监听数组的 __proto__ 指向的是代理原型对象,在代理原型对象中扩展了能改变数组数据结构的方法。在代理原型对象的方法中,需要做2件事。第一件事就是方法的原有行为不变,第二件事为做一些拦截操作,将改变数组结构的操作加入到响应式机制中,去通知触碰了这个数组的所有观察者数组已发生变化。

回到前言中的问题,Vue 2.x 为什么不监听数组下标索引值的变化?原因就是在于 Vue2 的数组响应式是通过代理原型对象实现了,只有调用数组原型对象上的方法改变数组元素时才可以监听到变化。当然也可以为数组的每个元素设置 getter 和 setter,但这样会增加很多不必要的性能损耗。

因此,Vue 提供了一个方法 Vue.set,使用这个方法改变数组和对象的元素可以响应性的被监听到。

二、Vue3 响应式

在 Vue3 中,使用 Proxy 来实现响应性。

Vue3 使用了 Proxy 可以解决 Vue2 中两个不能直接通过语法来解决的问题:

(1) 对新增的响应式对象属性的监听。

(2) 通过数组下标修改数组元素的响应性。

1. Proxy

Proxy 可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截。因此提供了一种机制,可以对外界的访问进行过滤和改写。

2. Vue3 响应式

再来看上篇文章的示例:

const data: DataProps = reactive({
  count: 0,
  double: computed(() => data.count * 2),
  increase: () => {
    data.count++;
  },
});

修改示例代码:

const data: DataProps = reactive({
  count: 0,
  double: computed(() => data.count * 2),
  nums: [1, 2, 3, 4, 5],
  increase: () => {
    data.count++;
    for (let i = 0; i < data.nums.length; i++) {
      data.nums[i]++;
    }
  },
});

在 data 中新增一个数组 nums,元素为 1、2、3、4、5。修改方法 increase,对数组中的所有元素进行自增。

修改模板部分,添加一个 ul 列表,展示 nums 中的元素值。

  • {{ item }}

如果使用 Vue2,显然 increase 方法是不会触发 li 元素值的变化。但在 Vue3 中,是可以响应式修改的。

进入预览页面,可以看到展示出了 nums 的元素:

Vue3-03-Vue2 响应式 VS Vue3 响应式_第2张图片

 点击 button 按钮,列表元素响应式的进行自增:

Vue3-03-Vue2 响应式 VS Vue3 响应式_第3张图片

3. Vue3 数组响应式的简单实现

简单实现代码如下:

var source = [0, 1, 2];

var wrapped = new Proxy(source, {
  get(target, p, receiver) {
    console.log('我侦测到你想对数组下标' + p + '进行访问');
    return Reflect.get(target, p, receiver);
  },
  set(target, p, value, receiver) {
    console.log('我侦测到你想对数组下标' + p + '进行赋值');
    return Reflect.set(target, p, receiver);
  }
});

wrapped[0];
wrapped[2] = 5;

粘贴到浏览器控制台运行,运行结果如下:

Vue3-03-Vue2 响应式 VS Vue3 响应式_第4张图片

可以看到,使用 Proxy 成功监测到了数组下标元素的变化。

三、总结

1. Object.defineProperty 是对对象属性的劫持;Proxy 是对整个对象的劫持。

2. Object.defineProperty 需要手动绑定响应式对象新增属性的响应性;Proxy 能侦测对象新增属性并实时响应。

3. Object.defineProperty 无法侦测数组元素的变化,需要手动设置代理原型;Proxy 能监听数组。

4. Object.defineProperty 启动就递归,不管对象嵌套多深,都把对象上的 property 转化为响应式的;Proxy 只在 getter 时才进行对象下一层属性的劫持。

5. Object.defineProperty 无兼容性问题;Proxy 有兼容性问题(IE)。

你可能感兴趣的:(Vue3,vue3,前端)