Vue.js的核心理念便是响应式,通过其响应式原理实现了数据的自动更新和视图的动态更新。响应式的核心概念是数据劫持(data reactivity)和依赖追踪(dependency tracking)。

本文分别讲解了Vue3和Vue2的响应式原理和区别

Vue2响应式原理:

  1. 初始化阶段:

    • Vue2采用选项式API(Options API),我们可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods、mounted等。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。属性都采用配置的方法写在data属性中,在组件实例化过程中,Vue将通过遍历组件选项中的data属性,将数据对象转换为响应式对象。
    • 对于每个属性,Vue会使用Object.defineProperty方法设置getter和setter,以便在属性被访问和修改时进行拦截。
    • 但需要注意的是,只有在组件创建时就已经存在于data属性的propety中的数据才是响应式的。
  2. 依赖追踪:

    • 当渲染组件时,Vue会运行渲染函数,该函数会读取组件中的数据,并建立数据与视图之间的依赖关系。
    • 在getter访问器中,Vue会将当前观察者(watcher)添加到依赖关系中,以此建立起数据属性和观察者之间的关系。
  3. 数据响应:

    • 当数据发生变化时,setter会被调用,并通知所有依赖于该数据的观察者。
    • 通知触发后,观察者将重新运行渲染函数,更新相关的视图。
  4. 虚拟DOM更新:

    • 在重新运行渲染函数时,Vue会比较新旧虚拟DOM树之间的差异,并进行最小化的DOM操作,以提高性能。

以下是使用Object.defineProperty实现一个简化版的Vue响应式原理:

  1. 创建一个Observer对象,用于将对象转化为响应式对象。
function Observer(obj) {
  Object.keys(obj).forEach(function(key) {
    defineReactive(obj, key, obj[key]);
  });
}

function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get: function() {
      // 在这里可以添加依赖收集的逻辑
      return value;
    },
    set: function(newValue) {
      // 在这里可以添加派发更新的逻辑
      value = newValue;
      // 在这里可以添加通知更新的逻辑,比如重新更新虚拟dom
    }
  });
}

创建一个Vue构造函数,其中的data选项通过Observer对象进行转化为响应式对象。

function Vue(options) {
  this.data = options.data;
  new Observer(this.data);
}

这样,在创建Vue实例时,传入的data选项会被转化为响应式对象。

var vm = new Vue({
  data: {
    message: 'Hello, Vue!'
  }
});

现在vm.data.message就成为了响应式对象。

Vue3响应式原理:

核心概念:

Vue 3 的响应式原理基于 ES6 的 Proxy 对象,同样是通过依赖收集和触发更新来实现数据的自动更新。Proxy是ES6中新增的特性,它允许你创建一个对象的代理,从而可以拦截并定义基本操作的行为,比如读取属性、写入属性等,不同的是Vue3使用的Proxy可以深度监听属性变化,对于一些对象、数组也可以实现响应式。

说直白一点就是给你想要绑定响应式的数据,用ref或者reactive包裹起来,然后其中的数据> 就会被创建一个Proxy代理,然后当你访问或修改这个数据的时候,就会触发他的get和set方> 法,从而实现响应式。

ref

在Vue 3中,ref是一个用于创建响应式引用的函数。它接受一个参数作为初始值,并返回一个包含value属性的对象,该对象是Proxy的代理对象。当我们访问ref创建的对象的value属性时,Proxy会捕获这个操作,并将其标记为依赖。当value属性发生变化时,依赖会被触发,从而更新相关的视图。

reactive

除了ref,Vue 3还提供了reactive函数来创建一个包含响应式属性的对象。与ref不同,reactive接受一个普通对象作为参数,并返回一个代理对象。这个代理对象会监听对象内部的所有属性,并在属性发生变化时触发相应的更新。

Vue 3的响应式实现

以下是refreactive的简单实现方式。

ref的简单实现

function ref(value) {
  return {
    value: new Proxy({ _value: value }, {
      get(target, key) {
        return target[key];
      },
      set(target, key, newValue) {
        target[key] = newValue;
        // 触发更新
      }
    })
  };
}

const count = ref(0);
console.log(count.value); // 输出:0
count.value = 1; // 触发更新
console.log(count.value); // 输出:1

reactive的简单实现

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      // ...
    },
    set(target, key, value) {
      // ...
      // 触发更新
    }
  });
}

const state = reactive({ count: 0 });
console.log(state.count); // 输出:0
state.count = 1; // 触发更新
console.log(state.count); // 输出:1

这个示例中,当我们访问state.count时,实际上是访问了Proxy对象的get方法,在这个方法中我们可以捕获到这个操作,并进行相应的一些处理。

总的来说

Vue2和Vue3都是采用数据拦截和发布订阅模式,通过依赖收集和触发更新来实现数据的自动更新,但是Vue3使用的Proxy可以深度监听属性变化,对于一些对象、数组也可以实现响应式,在性能方面更加优于Vue2。

为什么Proxy优于Vue2的defineProperty?、

在 Vue2 中,实现响应式数据主要依赖于 Object.defineProperty 方法,它将数据对象的属性转换为 getter 和 setter,从而在访问和修改属性时触发对应的操作。但是 Object.defineProperty 有其局限性,它需要遍历对象的每个属性并将其转换为响应式,如果对象的层级较深,这种操作可能会导致性能损失。此外,Object.defineProperty 只能监听属性的读取和赋值操作,不能监听对象的新增或删除等操作。

Vue3 为了优化性能,引入了 ES6 的 Proxy 对象,它可以直接监听整个对象,而不需要像 Object.defineProperty 那样遍历属性。使用 Proxy 对象可以直接监听对象的变化,包括属性的读取、赋值、删除等操作,这使得 Vue3 的响应式系统更加高效。

示例

下面是一个简单的示例,演示了使用 Vue2 和 Vue3 实现响应式的对比:

Vue2 示例

Copy code
// Vue2中通过Object.defineProperty实现响应式
const data = { count: 0 };
Object.defineProperty(data, 'count', {
  get() {
    console.log('get count:', data.count);
    return data.count;
  },
  set(newVal) {
    console.log('set count:', newVal);
    data.count = newVal;
  },
});

console.log(data.count); // 输出: get count: 0, 0
data.count = 1; // 输出: set count: 1
console.log(data.count); // 输出: get count: 1, 1

Vue3 示例

Copy code
// Vue3中通过Proxy实现响应式
const data = new Proxy({ count: 0 }, {
  get(target, key) {
    console.log('get', key);
    return target[key];
  },
  set(target, key, value) {
    console.log('set', key, value);
    target[key] = value;
  },
});

console.log(data.count); // 输出: get count, 0
data.count = 1; // 输出: set count 1
console.log(data.count); // 输出: get count 1

在这两个示例中,我们分别使用了 Vue2 和 Vue3 的方式实现了一个简单的响应式数据。可以看到,在 Vue2 中我们需要使用 Object.defineProperty 来手动定义 getter 和 setter,而在 Vue3 中我们直接使用了 Proxy 对象来监听整个数据对象的变化,这就是为什么Proxy整体要优于Object.defineProperty的原因