vue中的响应式

209 阅读2分钟

vue中的响应式

一、数据劫持的实现方式

1. Object.defineProperty() 方法

在 Vue 2 中,主要使用 `Object.defineProperty()` 来实现数据劫持。这个方法可以为对象的属性定义 getter(获取属性值时触发)和 setter(设置属性值时触发)。

例如:
    let data = {
      name: '张三'
    };

    Object.defineProperty(data, 'name', {
      get() {
        console.log('数据被读取');
        return this._name;
      },
      set(newValue) {
        console.log('数据被修改');
        this._name = newValue;
      }
    });

在上述代码中:

  • get 函数:当我们读取 data.name 时,会触发这个函数。函数内部的 console.log('数据被读取') 会输出相应的提示信息,然后返回 this._name 的值。

  • set 函数:当我们给 data.name 赋值时,会触发这个函数。例如执行 data.name = '李四' ,console.log('数据被修改') 会输出提示,然后将新的值 newValue 赋给 this._name 。

但 Object.defineProperty() 有一些局限性,比如无法监听数组的新增、删除操作,只能监听属性的赋值操作。

Object.defineProperty 主要是用于定义对象属性的特性,比如属性的可写性、可枚举性、可配置性,以及设置属性的 getter 和 setter 方法来监听属性的读取和写入操作。

但是对于数组来说,它有一些特殊的方法来进行元素的添加、删除和修改,比如 push 、 pop 、 shift 、 unshift 、 splice 等。

Object.defineProperty 只能针对已经明确定义的属性进行监听,无法直接感知到通过这些数组方法引起的数组变化。

打个比方,Object.defineProperty 就像是一个只能看守特定房间的守卫,只能注意到这个特定房间里物品的直接变动。而数组的那些方法导致的变化,就像是从别的通道进入这个区域的变动,这个守卫是注意不到的。

在 Vue 2 中,为了能监听数组的这些变化,不得不对这些数组方法进行了特殊的处理和重写,这增加了实现的复杂性和可能出现兼容性问题的风险。

2. Proxy 方法

在 Vue 3 中,引入了 `Proxy` 来实现数据劫持,它提供了更强大和灵活的功能。

例如:
    let data = {
      name: '李四'
    };

    let proxyData = new Proxy(data, {
      get(target, key) {
        console.log(`读取属性: ${key}`);
        return target[key];
      },
      set(target, key, value) {
        console.log(`设置属性: ${key}${value}`);
        target[key] = value;
      }
    });

在这个例子中:

  • get 函数:当我们通过 proxyData[key] 读取属性时,会触发这个函数。它会输出读取的属性名,并返回原始对象 target 中对应属性的值。
  • set 函数:当我们通过 proxyData[key] = value 设置属性时,会触发这个函数。它会输出设置的属性名和新的值,并在原始对象 target 中更新对应属性的值。

Proxy 可以监听更多的操作,比如数组的方法(如 push 、 pop 等)。

为什么vue3的poxy比vue2的defineprorty更快

Vue 3 中的 Proxy 比 Vue 2 中的 defineProperty 更快,主要有以下几个关键原因:

  1. 监听机制的差异

    • defineProperty 需要为对象的每个属性单独进行定义和处理,这在处理具有大量属性的对象时会产生较大的性能开销。
    • Proxy 则是直接对整个对象进行监听,不需要逐个属性设置,减少了大量的重复操作。
  2. 对嵌套对象的处理

    • 当对象存在多层嵌套结构时,defineProperty 需要递归地为每个嵌套的属性添加监听,这会导致性能急剧下降。
    • 而 Proxy 能够原生地监听嵌套对象的变化,无需复杂的递归处理,大大提高了处理嵌套对象时的效率。
  3. 对数组的处理

    • defineProperty 对数组的监听实现较为复杂,需要重写数组的方法来模拟监听,这不仅增加了代码的复杂性,还可能引入兼容性问题,并且性能不佳。

    • Proxy 可以直接原生地监听数组的各种操作,如元素的添加、删除、修改等,无需额外的特殊处理,性能更优。

例如,如果有一个复杂的对象结构,包含大量属性和多层嵌套,使用 defineProperty 进行监听和更新可能会导致明显的性能瓶颈,页面响应变得迟缓。而在 Vue 3 中使用 Proxy 则能够更高效地处理这种复杂的情况,保持页面的流畅性和响应性。

综上所述,Proxy 的整体监听机制和对复杂数据结构的处理方式使其在性能上优于 defineProperty,这也是 Vue 3 在性能方面的一个重要改进。

二、依赖收集的过程

当组件进行渲染或计算属性进行求值时,会触发数据的读取操作,从而进行依赖收集。

例如,有一个组件模板如下:

<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!'
    };
  }
};
</script>

当 Vue 开始渲染这个组件时,会解析模板中的表达式 {{ message }} ,从而读取 message 的值。在这个读取过程中,Vue 会将当前组件标记为 message 数据的依赖。

这意味着,如果 message 的值发生了变化,Vue 就知道需要重新渲染这个组件,以更新视图。

三、发布/订阅模式的运作

当数据发生变化,比如我们修改了 message 的值:

this.message = 'Hello World!'

Vue 会遍历所有依赖 message 数据的组件和计算属性,并通知它们进行更新。

假设还有另一个组件也使用了 message 数据:

<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  data() {
    return {};
  },
  computed: {
    formattedMessage() {
      return this.message.toUpperCase();
    }
  }
};
</script>

当 message 的值发生变化时,不仅上述两个组件会重新渲染,计算属性 formattedMessage 也会重新计算并更新。因为它们都是 message 数据的依赖者,当数据变化时,会收到通知并进行相应的处理。

ref

ref 用于创建一个响应式的基本类型值或对象属性。

import { ref } from 'vue';

// 基本类型
const count = ref(0);
console.log(count.value);  // 0

count.value++; 
console.log(count.value);  // 1

// 对象属性
const person = { age: ref(20) };
console.log(person.age.value);  // 20
person.age.value = 21;
console.log(person.age.value);  // 21

reactive

reactive 用于创建一个响应式的复杂对象(对象或数组)。

import { reactive } from 'vue';

const user = reactive({ name: '张三', age: 20 });
user.age = 21;
console.log(user.age);  // 21

shallowRef

shallowRef 创建的响应式数据,只有值本身是响应式的,对象内部属性不是。

import { shallowRef } from 'vue';

const obj = shallowRef({ name: '李四' });
obj.value.name = '王五';  // 不会触发响应式更新

obj.value = { name: '赵六' };  // 会触发更新

shallowReactive

shallowReactive 创建的响应式对象,只有第一层属性是响应式的,嵌套对象属性不是。

import { shallowReactive } from 'vue';

const obj = shallowReactive({ user: { name: '小明' } });
obj.user.name = '小红';  // 不会触发响应式更新

obj.user = { name: '小刚' };  // 会触发更新

watch

watch 用于监视特定的响应式数据的变化。

import { ref, watch } from 'vue';

const count = ref(0);

watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为了 ${newValue}`);
});

count.value++; 

watchEffect

watchEffect 会立即执行副作用函数,并在依赖变化时重新执行。

import { ref, watchEffect } from 'vue';

const count = ref(0);

watchEffect(() => {
  console.log(`count 的值是: ${count.value}`);
});

count.value++;