再也不怕面试官问watch、computed、watchEffect的区别了

开发 前端
watch​适用于需要有条件地监听数据变化的场景,computed​适用于创建派生数据和性能优化,而watchEffect适用于自动追踪依赖的场景。在实际应用中,根据具体需求选择合适的API可以更好地发挥Vue的响应式能力。

在Vue中,数据响应式是一个核心概念,它使得当数据变化时,相关的视图会自动更新。为了更灵活地处理数据的变化,Vue提供了多种方式,其中包括watch、computed和watchEffect。

watch

watch是Vue中一个非常强大的特性,它允许你监听数据的变化并做出相应的反应。它有两种用法:一是监听一个具体的数据变化,二是监听多个数据的变化。

// 监听单个数据
watch('someData', (newVal, oldVal) => {
  // 做一些事情
});

// 监听多个数据
watch(['data1', 'data2'], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
  // 做一些事情
});

watch的实现原理

Vue中watch的实现主要依赖于Watcher这个核心类。当调用watch时,实际上是创建了一个Watcher实例,并将回调函数和需要监听的数据传递给这个实例。

// 简化版的watch实现
function watch(source, cb) {
  const watcher = new Watcher(source, cb);
}

Watcher类的构造函数接收两个参数,分别是需要监听的数据(可以是一个字符串,也可以是一个返回值的函数)和回调函数。在构造函数中,会对数据进行求值,然后将这个Watcher实例添加到数据的依赖列表中。

class Watcher {
  constructor(source, cb) {
    this.getter = typeof source === 'function' ? source : () => this.vm[source];
    this.cb = cb;
    this.value = this.get();
  }

  get() {
    pushTarget(this); // 将当前Watcher实例压栈
    const value = this.getter.call(this.vm); // 触发数据的getter,将当前Watcher实例添加到依赖列表中
    popTarget(); // 将当前Watcher实例出栈
    return value;
  }

  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb(this.value, oldValue);
  }
}

简单来说,watch的实现原理就是通过Watcher实例来监听数据的变化,当数据变化时,触发update方法执行回调函数。

computed

computed用于计算派生数据。它依赖于其他响应式数据,并且只有在相关数据发生变化时才会重新计算。

computed(() => {
  return someData * 2;
});

computed的实现原理

computed的实现原理相对于watch更为复杂,它依赖于getter和setter的机制。在Vue中,computed的定义是一个包含get和set方法的对象。

const computed = {
  get() {
    return someData * 2;
  },
  set(value) {
    someData = value / 2;
  }
};

在computed的实现中,当访问计算属性时,实际上是执行了get方法,而在数据变化时,会执行set方法。这里主要使用了Object.defineProperty这个JavaScript的特性。

function createComputedGetter() {
  return function computedGetter() {
    const value = getter.call(this); // 执行计算属性的get方法
    track(target, TrackOpTypes.GET, 'value'); // 添加依赖
    return value;
  };
}

function createComputedSetter() {
  return function computedSetter(newValue) {
    setter.call(this, newValue); // 执行计算属性的set方法
    trigger(target, TriggerOpTypes.SET, 'value'); // 触发更新
  };
}

function computed(getterOrOptions) {
  const getter = 
    typeof getterOrOptions === 'function'
      ? getterOrOptions
      : getterOrOptions.get;

  const setter = getterOrOptions.set;

  const cRef = new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.get
  );

  return cRef;
}

class ComputedRefImpl {
  // 构造函数
  constructor(getter, setter, isReadonly) {
    // ...
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true;
          triggerRef(this);
        }
      },
    });
  }
  // ...
}

在上述代码中,createComputedGetter和createComputedSetter用于创建计算属性的getter和setter。computed函数接收一个getter函数,并通过Object.defineProperty将getter和setter添加到计算属性的引用对象中。

当计算属性被访问时,会触发getter,此时会将当前计算属性添加到依赖列表中。当计算属性的依赖数据发生变化时,会触发setter,并通过triggerRef触发计算属性的更新。

watchEffect

watchEffect是Vue 3新增的特性,它用于监听一个函数内部的响应式数据变化,当变化时,函数会被重新执行。

watchEffect(() => {
  // 依赖于响应式数据的操作
});

watchEffect的实现原理

watchEffect是Vue 3中引入的响应式API,它用于执行一个响应式函数,并在函数中响应式地追踪其依赖。与watch不同,watchEffect不需要显式地指定依赖,它会自动追踪函数内部的响应式数据,并在这些数据变化时触发函数重新执行。

以下是watchEffect的简单用法:

import { watchEffect, reactive } from 'vue';

const state = reactive({
  count: 0,
});

watchEffect(() => {
  console.log(state.count);
});

在这个例子中,watchEffect内部的函数会自动追踪state.count的变化,并在其变化时触发函数执行。

现在,让我们来探讨watchEffect的实现原理。

首先,watchEffect的核心是依赖追踪和触发。Vue 3中的响应式系统使用ReactiveEffect类来表示一个响应式的函数。

class ReactiveEffect {
  constructor(fn, scheduler = null) {
    // ...
    this.deps = [];
    this.scheduler = scheduler;
  }

  run() {
    // 执行响应式函数
    this.active && this.getter();
  }

  stop() {
    // 停止追踪
    cleanupEffect(this);
  }
}

export function watchEffect(effect, options = {}) {
  // 创建ReactiveEffect实例
  const runner = effect;
  const job = () => {
    if (!runner.active) {
      return;
    }
    if (cleanup) {
      cleanup();
    }
    // 执行响应式函数
    return runner.run();
  };
  // 执行响应式函数
  job();
  // 返回停止函数
  return () => {
    stop(runner);
  };
}

在上述代码中,ReactiveEffect类表示一个响应式的函数。watchEffect函数接收一个响应式函数,并创建一个ReactiveEffect实例。在执行时,该实例会追踪函数内部的响应式数据,并在这些数据变化时触发函数重新执行。

watchEffect返回一个停止函数,用于停止对响应式数据的追踪。

实际开发当中该怎么去选择

watch

watch主要用于监听特定的数据变化并执行回调函数。它可以监听数据的变化,并在满足一定条件时执行相应的操作。常见的使用场景包括:

  1. 异步操作触发:当某个数据发生变化后,需要进行异步操作,比如发起一个网络请求或执行一段耗时的操作。
watch(() => state.data, async (newData, oldData) => {
  // 异步操作
  await fetchData(newData);
});
  1. 深度监听:监听对象或数组的变化,并在深层次的数据变化时执行回调。
watch(() => state.user.address.city, (newCity, oldCity) => {
  console.log(`City changed from ${oldCity} to ${newCity}`);
});

computed

computed用于创建一个计算属性,它依赖于其他响应式数据,并且只有在依赖数据发生变化时才重新计算。常见的使用场景包括:

  1. 派生数据:根据现有的数据计算出一些派生的数据,而不必每次都重新计算。
const fullName = computed(() => `${state.firstName} ${state.lastName}`);
  1. 性能优化:避免不必要的重复计算,提高性能。
const result = computed(() => {
  // 避免重复计算
  if (someCondition) {
    return heavyCalculation();
  } else {
    return defaultResult;
  }
});

watchEffect

watchEffect用于执行一个响应式函数,并在函数内部自动追踪依赖。它适用于不需要显式指定依赖,而是在函数内部自动追踪所有响应式数据变化的场景。常见的使用场景包括:

  1. 自动依赖追踪:函数内部的所有响应式数据都被自动追踪,无需显式指定。
watchEffect(() => {
  console.log(`Count changed to ${state.count}`);
});
  1. 动态数据处理:处理动态变化的数据,无需手动管理依赖。
watchEffect(() => {
  // 处理动态变化的数据
  handleDynamicData();
});

总体而言,watch适用于需要有条件地监听数据变化的场景,computed适用于创建派生数据和性能优化,而watchEffect适用于自动追踪依赖的场景。在实际应用中,根据具体需求选择合适的API可以更好地发挥Vue的响应式能力。


责任编辑:武晓燕 来源: 前端大湿兄
相关推荐

2021-05-08 07:53:33

面试线程池系统

2022-04-01 07:52:42

JavaScript防抖节流

2020-11-24 07:48:32

React

2021-08-10 18:36:02

Express原理面试

2020-10-20 09:12:57

axios核心原理

2022-10-31 11:10:49

Javavolatile变量

2020-10-15 12:52:46

SpringbootJava编程语言

2022-08-27 13:49:36

ES7promiseresolve

2021-04-22 07:49:51

Vue3Vue2.xVue3.x

2020-10-23 09:26:57

React-Redux

2022-07-14 08:22:48

Computedvue3

2022-07-11 07:36:36

缓存缓存雪崩缓存击穿

2021-12-25 22:31:10

MarkWord面试synchronize

2021-11-08 09:18:01

CAS面试场景

2019-06-17 05:03:37

memcache内核架构

2020-04-30 10:24:35

Spring循环依赖Java

2021-12-16 18:38:13

面试Synchronize

2020-04-20 15:00:22

DevOps工具代码

2020-07-28 00:58:20

IP地址子网TCP

2021-12-02 18:20:25

算法垃圾回收
点赞
收藏

51CTO技术栈公众号