vue2和vue 3 的响应式原理

vue 3 响应式原理

在 Vue 3 中,响应式系统的核心是使用了 ES6 的 Proxy 对象来实现对数据的拦截和响应式更新。

简单的 Proxy 示例:

const data = { count: 0 };
const handler = {
  get(target, key, receiver) {
    // 当访问属性时触发
    track(target, key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    // 当设置属性时触发
    const result = Reflect.set(target, key, value, receiver);
    trigger(target, key);
    return result;
  }
};
​
const reactiveData = new Proxy(data, handler);
const proxy = new Proxy(target, handler);
​
console.log(proxy.message); // "Getting property message" -> "Hello, world!"
proxy.message = "Hello, Vue 3!"; // "Setting property message to Hello, Vue 3!"

在上面的代码中,handler 对象定义了两个拦截器方法:getset。这两个方法分别用于拦截属性的读取和设置操作。

  • get 方法在属性被访问时触发。它接收三个参数:目标对象(target)、属性名(key)和代理对象(receiver)。在 get 方法中,你可以执行一些操作,比如追踪依赖(track),然后返回属性的值。

  • set 方法在属性被设置时触发。它接收四个参数:目标对象(target)、属性名(key)、新值(value)和代理对象(receiver)。在 set 方法中,你可以执行一些操作,比如触发依赖更新(trigger),然后返回一个布尔值表示设置操作是否成功。

在 Vue 3 中,Proxy 被用来实现响应式数据追踪和依赖收集。主要通过 reactiveref API 实现。

Reactive API 示例

import { reactive } from 'vue';
​
const state = reactive({
  count: 0
});
​
state.count++; // 这将触发 Vue 的响应式系统

Ref API 示例:

import { ref } from 'vue';
​
const count = ref(0);
​
count.value++; // 这也将触发 Vue 的响应式系统

响应式系统的工作原理

  1. 依赖收集:当响应式对象的属性被访问时,Proxyget 拦截器会触发依赖收集,将依赖(如组件或计算属性)记录下来。

  2. 触发更新:当响应式对象的属性被修改时,Proxyset 拦截器会触发通知,所有依赖于该属性的组件或计算属性都会重新计算或重新渲染。

简化的响应式系统示例:

import { reactive, effect } from 'vue';
const state = reactive({ count: 0 });
​
effect(() => {
  console.log(`Count is: ${state.count}`);
});
​
state.count++;
// 当 state.count 被修改时,上面的 effect 会重新执行,输出 "Count is: 1"

vue2 的响应式原理

Object.defineProperty 是 ES5 引入的一个方法,用于直接在一个对象上定义或修改一个属性,同时可以指定该属性的特性,如是否可枚举、是否可配置、是否可写等。

当你访问一个已经被 Vue 转换过的属性时,Vue 会将这个属性标记为“依赖”,然后当这个属性被修改时,Vue 会通知所有依赖于这个属性的地方进行更新。这也就实现了 Vue 的响应式系统。当你在模板中使用了一个 data 对象的属性时,比如 {{ message }},Vue 会在内部追踪 message 属性的依赖关系,当 message 发生变化时,Vue 会自动更新相关的视图。

定义响应式属性:Vue 定义响应式属性的核心逻辑如下:

function defineReactive(obj, key, val) {
  // 创建一个依赖管理器(Dep),用于收集和通知依赖
  const dep = new Dep();
  // 获取对象属性的当前描述符
  let property = Object.getOwnPropertyDescriptor(obj, key);
​
  // 如果不可配置,则直接返回
  if (property && property.configurable === false) {
    return;
  }
  // 缓存属性的 getter 和 setter(如果存在)
  const getter = property && property.get;
  const setter = property && property.set;
​
  // 使用 Object.defineProperty 重新定义属性
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 获取值
      const value = getter ? getter.call(obj) : val;
      // 如果存在当前依赖目标,则添加依赖
      if (Dep.target) {
        dep.depend();
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      // 如果新值与旧值相同,则不触发更新
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      // 如果存在 setter,则调用 setter,否则直接设置值
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 通知所有依赖进行更新
      dep.notify();
    }
  });
}
  1. 依赖管理器 (Dep):这是一个简单的依赖管理器,用于收集依赖并在数据变化时通知它们:

let uid = 0;
​
class Dep {
  constructor() {
    this.id = uid++;
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    const index = this.subs.indexOf(sub);
    if (index > -1) {
      this.subs.splice(index, 1);
    }
  }
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
  notify() {
    const subs = this.subs.slice();
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}
Dep.target = null;

假设我们有一个 Vue 实例,其 data 对象中有一个属性 message

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

当你在 Vue 实例的模板中使用 {{ message }} 时,Vue 会创建一个“Watcher”来追踪这个属性的依赖:

watcher = new Watcher(vm, function updateComponent() {
  console.log(vm.message); // 读取 message 属性会触发 getter
});

当你修改 vm.message 的值时,setter 会被触发,并且 dep.notify() 会通知所有依赖于 message 的 watcher 更新,从而触发视图更新。

vm.message = 'Hello World!'; // 触发 setter,调用 dep.notify(),视图更新

Object.defineProperty与 proxy特点:

  • 局限性Object.defineProperty 只能对对象的属性进行拦截,不能拦截对象的新增属性、删除属性等操作。

  • 性能:在处理大量属性时,Object.defineProperty 的性能可能不如 Proxy,因为 Object.defineProperty 需要为每个属性单独设置拦截器。

  • 兼容性Object.defineProperty 在旧版浏览器中可能不被支持,而 Proxy 需要 ES6 环境。

    object.defineProperty 示例

    const obj = {};
    Object.defineProperty(obj, 'name', {
      get() {
        console.log('访问 name 属性');
        return '张三';
      },
      set(value) {
        console.log('设置 name 属性', value);
      }
    });
    ​
    console.log(obj.name); // 输出 '访问 name 属性' 和 '张三'
    obj.name = '李四'; // 输出 '设置 name 属性' 和 '李四'
    ​

    proxy 示例

    const handler = {
      get(target, propKey, receiver) {
        console.log(`访问 ${propKey} 属性`);
        return Reflect.get(target, propKey, receiver);
      },
      set(target, propKey, value, receiver) {
        console.log(`设置 ${propKey} 属性为 ${value}`);
        return Reflect.set(target, propKey, value, receiver);
      }
    };
    const proxy = new Proxy({}, handler);
    ​
    proxy.name = '王五'; // 输出 '设置 name 属性为 王五'
    console.log(proxy.name); // 输出 '访问 name 属性' 和 '王五'

需要注意的点:

1、Object.defineProperty 有一些限制,它只能监听对象的属性,并且需要遍历对象的属性来进行转换,这意味着在 Vue 2 中无法监听数组的变化。因此,在 Vue 2 中对数组的变化需要通过特定的方法来进行处理,比如使用 pushpop 等方法,或者直接使用 Vue.set 方法。

2、因此vue2 可能出现的一个问题,数据更新视图未更新的情况。但是vue2 推出了this.$set(target,key,修改后的值)。在vue3中使用了proxy 代理就很好的解决了这个问题。

3、异步更新问题:有些情况下,如果数据的变化发生在 JavaScript 执行栈之外(比如在定时器回调、Promise 回调中),Vue 可能无法立即捕获到数据的变化。这时可以使用 this.$nextTick() 来确保在 DOM 更新之后再执行一段代码。

相关推荐

  1. vue2vue 3 响应原理

    2024-06-16 13:14:02       32 阅读
  2. vue2响应vue3响应

    2024-06-16 13:14:02       26 阅读
  3. Vue3响应原理

    2024-06-16 13:14:02       64 阅读
  4. Vue 3响应原理

    2024-06-16 13:14:02       64 阅读
  5. Vue2源码】响应原理

    2024-06-16 13:14:02       41 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-16 13:14:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-16 13:14:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-16 13:14:02       82 阅读
  4. Python语言-面向对象

    2024-06-16 13:14:02       91 阅读

热门阅读

  1. 博客摘录「 YOLOv5模型剪枝压缩」2024年5月11日

    2024-06-16 13:14:02       37 阅读
  2. 主流排序算法——python

    2024-06-16 13:14:02       36 阅读
  3. make menuconfig 分析

    2024-06-16 13:14:02       32 阅读
  4. leecode N皇后

    2024-06-16 13:14:02       35 阅读
  5. HTML基础标签解析:H1-H6、DIV与P的正确使用方法

    2024-06-16 13:14:02       26 阅读
  6. Vue3 中 props 与 emit 用法

    2024-06-16 13:14:02       35 阅读
  7. django orm 查询返回指定关键字

    2024-06-16 13:14:02       35 阅读
  8. 深度学习500问——Chapter12:网络搭建及训练(1)

    2024-06-16 13:14:02       26 阅读
  9. 【AI原理解析】— 星火大模型

    2024-06-16 13:14:02       32 阅读
  10. 基于SpringBoot+Spark搭建本地计算引擎服务

    2024-06-16 13:14:02       38 阅读