深入了解Vue2和Vue3的响应式原理

想必大家在学习vue的时候都会有这样的疑问,自己在学习JavaScript的时候,不论要修改什么内容,只有在页面刷新的时候,我们的值才会发生更新变化,但是当我们在一个vue项目中进行一样的操作的时候,就可以实现实时的变化,这是为什么呢,这是因为vue当中可以实现响应式数据更新,什么是响应式数据更新?到底是通过什么来进行实现的呢?

vue2中的响应式原理:

vue2中是通过Object.defineProperty () 来实现响应式数据更新的,当你创建了一个vue对象,并且向它的data里面添加了部分数据,这些就变成了响应式数据,vue会遍历所有的数据,然后使用Object.defineProperty () 把这些数据添加上getter和setter,这样再我们的vue当中就会自动追踪依赖,当这些数据property被访问和修改时进行通知变更。所以就是说,我们的响应式是通过getter和setter来实现数据实时更新的,当响应式数据的值通过setter被修改时,vue 会通知所有与此数据相关的所有 Watcher。这些 Watcher 会记录下数据的变化,并准备更新视图。

watchervue2响应式系统的"中枢神经系统",扮演着重要的角色,它主要会承担三大核心职责,

1.收集依赖 2.变更检测 3.更新调度,这里我就不细讲了大家可以去了解一下

但是需要注意的是,vue不能检测到数组和对象的变化,这是因为我们是通过索引来进行更新赋值的,但是索引赋值我们的更新不会触发setter函数,因为只有当我们的数据更新触发了setter函数,才能实现响应式更新,但是当我们使用数组的一些方法的时候,比如 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 等。这些方法会触发响应式更新。

下面举一个例子:

  • {{ item }}

这里实现的效果就是,当我们调用updateByIndex()方法的时候,我们的数组的值会被修改,但是我们的vue页面不会监听到这个数据的变化,就不会更新,打印的时候是【4,2,3】,但是页面上显示的还是【1,2,3】,但是当我们去调用updateBySplice()这个方法,不管是我们的打印还是页面上的更新,都显示的【4,2,3】,这就说明了我们的splice可以调用我们的setter方法从而实现页面的响应式数据更新,这个情况就可以证明的想法

vue3中的响应式原理:

vue3中是通过proxy来实现的响应式对象,我们在vue3中实现响应式对象的方法有两个,一个是ref,一个是reactive,这两个的区别就是ref可以用于所有的类型,但是reactive只能用于数组和对象类型,ref如果封装的是一个简单类型的对象,那么它不会使用proxy进行状态管理,它会使用gettersetter方法来进行管理,因为简单类型的响应式操作比较简单,proxy中会封存很多的方法,所以我们不在proxy中实现响应式数据更新,这样可以提高性能,而且proxy也只能用在数组或者对象这种类型,不能用于简单类型的。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

track是一个核心函数,用于在访问响应式对象属性的时候进行收集依赖

使用effect()定义一个副作用函数,该函数会读取 数据的值并打印到控制台。当 effect() 执行时,会将当前函数设置为activeEffect,并在读取数据时触发track函数,完成依赖收集。

trigger函数是用于触发依赖更新的关键部分。它的主要作用是:当数据发生变化时,通知所有依赖该数据的副作用函数(如组件的渲染函数)重新执行,从而实现视图的自动更新。

// 依赖收集和触发相关的全局变量
const targetMap = new WeakMap();

// 当前活跃的副作用函数
let activeEffect = null;

// 用于收集依赖的函数
function track(target, key) {
    if (activeEffect) {
        // 获取与目标对象关联的依赖映射,如果没有则创建一个新的 Map
        let depsMap = targetMap.get(target);
        if (!depsMap) {
            targetMap.set(target, (depsMap = new Map()));
        }
        // 获取与属性名关联的依赖集合,如果没有则创建一个新的 Set
        let dep = depsMap.get(key);
        if (!dep) {
            depsMap.set(key, (dep = new Set()));
        }
        // 将当前活跃的副作用函数添加到依赖集合中
        dep.add(activeEffect);
    }
}

// 用于触发依赖更新的函数
function trigger(target, key) {
    // 获取与目标对象关联的依赖映射
    const depsMap = targetMap.get(target);
    if (depsMap) {
        // 获取与被修改属性关联的依赖集合
        const dep = depsMap.get(key);
        if (dep) {
            // 遍历依赖集合,执行每个副作用函数
            dep.forEach(effect => {
                effect();
            });
        }
    }
}

// 创建响应式对象的函数
function reactive(target) {
    return new Proxy(target, {
        get(target, key, receiver) {
            // 触发依赖收集
            track(target, key);
            const res = Reflect.get(target, key, receiver);
            // 如果属性值是对象,则递归创建响应式对象
            if (typeof res === 'object' && res !== null) {
                return reactive(res);
            }
            return res;
        },
        set(target, key, value, receiver) {
            const oldValue = target[key];
            // 使用 Reflect.set 修改属性值
            const result = Reflect.set(target, key, value, receiver);
            // 如果新旧值不同,则触发依赖更新
            if (oldValue !== value) {
                trigger(target, key);
            }
            return result;
        }
    });
}

// 定义副作用函数的函数
function effect(fn) {
    // 将传入的函数作为当前活跃的副作用函数
    activeEffect = fn;
    // 执行副作用函数,进行依赖收集
    fn();
    // 执行完成后,重置当前活跃的副作用函数
    activeEffect = null;
}

// 示例:创建一个响应式对象并定义一个副作用函数
const state = reactive({ count: 0 });

// 定义一个副作用函数,每当 state.count 变化时,会重新执行
effect(() => {
    console.log(`count is: ${state.count}`);
});

// 修改 state.count,触发 trigger 函数,重新执行副作用函数
state.count = 1;  // 输出:count is: 1
state.count = 2;  // 输出:count is: 2

区别:

特性 Vue 2 Vue 3 核心差异说明
实现原理 Object.defineProperty Proxy Proxy 直接代理整个对象,无需递归初始化属性
数组响应式 需重写数组方法(push/pop/shift 等) 原生支持数组索引/长度修改 Vue 3 可直接通过索引修改数组(如 arr[0]=1)或修改 length
动态新增属性 Vue.set() / Vue.delete() 直接赋值生效 Vue 3 中 obj.newProperty = value 自动触发响应式
嵌套对象初始化 初始化时递归遍历所有属性 按需惰性代理(访问时触发) Vue 3 减少初始化开销,提升性能
性能优化 初始化时递归所有数据,性能压力较大 惰性代理 + 缓存机制,内存占用更低 大型对象/数组场景下 Vue 3 性能优势明显
响应式 API 仅通过 data 选项 提供 ref() / reactive() 等独立 API Vue 3 可在组件外灵活创建响应式数据
Map/Set 集合支持 不支持 原生支持 Vue 3 可直接响应 MapSetWeakMap 等集合类型的变化
调试能力 较弱 增强的调试钩子(onTrack/onTrigger Vue 3 提供响应式依赖追踪和触发调试工具

你可能感兴趣的:(vue.js,javascript,前端)