第 8 期 - Vue 响应式系统的原理、应用与性能优化
logoFRONTALK AI/10月31日 16:31/阅读原文

摘要

本文先阐述 Vue 响应式系统的概念,分别分析 Vue 2 和 Vue 3 的实现方式,介绍依赖收集与派发更新机制,再探讨其应用场景,最后给出性能优化策略。

一、Vue 响应式系统的基本概念

Vue 的响应式系统是数据驱动视图更新的关键,当数据变化时视图自动更新,无需手动操作 DOM。它主要由依赖收集和依赖追踪两个核心步骤组成,通过劫持对象属性的 getter 和 setter 来捕获数据的访问和修改以驱动 DOM 更新。并且在 Vue 3 中采用 Proxy 替代 Vue 2 的 Object.defineProperty,Proxy 在处理复杂情况时更灵活高效。

二、Vue 2 和 Vue 3 的实现方式

(一)Vue 2 中的实现方式:Object.defineProperty

在 Vue 2 中,响应式系统核心依赖 Object.defineProperty。对被观测对象遍历属性,通过Object.defineProperty为每个属性添加gettersetter实现数据拦截。

function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集
      console.log(`访问了属性 ${key}`);
      return val;
    },
    set(newVal) {
      if (val!== newVal) {
        val = newVal;
        // 触发更新
        console.log(`属性 ${key} 被修改为 ${newVal}`);
      }
    }
  });
}

这种方式在简单场景可行,但不能拦截动态添加属性或数组某些变动,需借助Vue.set和一些数组变异方法来处理。

(二)Vue 3 中的实现方式:Proxy

Vue 3 采用Proxy克服 Vue 2 的限制。Proxy可拦截对对象的所有操作,包括新增、删除属性、数组直接修改等。

const handler = {
  get(target, key) {
    // 依赖收集
    console.log(`读取属性 ${key}`);
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    // 触发更新
    console.log(`设置属性 ${key} 为 ${value}`);
    return Reflect.set(target, key, value);
  }
};
const obj = new Proxy({ name: 'Vue 3' }, handler);

Proxy解决了 Vue 2 的相关问题,还提升了性能与维护性。

三、依赖收集与派发更新

(一)依赖收集

组件渲染时数据getter触发,Vue 将当前组件渲染函数作为依赖收集到依赖管理器(Dep类)。

class Dep {
  constructor() {
    this.subscribers = new Set();
  }
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  notify() {
    this.subscribers.forEach(sub => sub());
  }
}
let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  effect(); // 触发 getter,进行依赖收集
  activeEffect = null;
}
const dep = new Dep();
const state = new Proxy({ count: 0 }, {
  get(target, key) {
    dep.depend();
    return target[key];
  },
  set(target, key, value) {
    target[key] = value;
    dep.notify(); // 触发更新
    return true;
  }
});

访问属性时(getter)收集依赖,数据变化(setter)时通知依赖重新计算或渲染。

(二)派发更新

数据setter触发时,Vue 通知依赖该数据的函数重新执行更新视图,这一过程叫派发更新,实现数据驱动视图自动更新,开发者只需关注业务逻辑。

四、响应式系统的应用场景

(一)表单数据的双向绑定

使用v - model可轻松实现表单数据双向绑定,自动监听输入框变化更新数据,数据变化时视图也自动更新,简化了 DOM 操作复杂性。

<template>
  <input v - model="name" placeholder="请输入名字">
  <p>您的名字是:{{ name }}</p>
</template>
<script>
export default {
  data() {
    return {
      name: ''
    };
  }
}
</script>

(二)实时数据更新

在股票、聊天等实时数据更新场景,响应式系统自动监听数据变化更新视图,无需手动触发。

<template>
  <p>当前股票价格:{{ stockPrice }}</p>
</template>
<script>
export default {
  data() {
    return {
      stockPrice: 0
    };
  },
  mounted() {
    setInterval(() => {
      this.stockPrice = this.getNewStockPrice();
    }, 1000);
  },
  methods: {
    getNewStockPrice() {
      return Math.floor(Math.random() * 1000); // 模拟股票价格更新
    }
  }
}
</script>

(三)缓存计算属性

计算属性基于现有状态计算新数据,根据依赖自动缓存和更新,适合多层级数据计算场景,可减少重复运算提高性能。

<template>
  <p>折扣价格:{{ discountedPrice }}</p>
</template>
<script>
export default {
  data() {
    return {
      price: 100,
      discount: 0.8
    };
  },
  computed: {
    discountedPrice() {
      return this.price * this.discount;
    }
  }
}
</script>

五、Vue 响应式系统的性能优化

(一)使用 v - once

对于不需要响应式更新的静态内容,使用v - once指令避免不必要重新渲染,如<p v - once>这段内容只会渲染一次</p>

(二)使用 watch 而非 computed 或 methods

某些场景下watch更适合监听特定数据变化,特别是需要在数据变化时执行异步操作时。

watch: {
  someData(newVal, oldVal) {
    // 当 someData 变化时执行逻辑
    this.doSomething(newVal);
  }
}

(三)避免过度嵌套的响应式对象

过度嵌套的对象结构会使 Vue 进行更深层次依赖追踪影响性能,设计数据结构时应避免深度嵌套或使用非响应式数据(如Object.freeze)避免不必要性能消耗。

 

扩展阅读

Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有