面试官:computed和watch的区别是什么?

大家好,今天文章由团队的 Uncle13 老师提供,题目也是来源于真实的面试。

“computed 和 watch 有哪些区别?”,今天的文章除了介绍两者的区别和使用场景,还会通过源码的剖析,带大家了解具体的实现原理。

以下是正文。

相信 Vue 技术栈的小伙伴都了解,Vue 提供了两种处理响应式数据的方式:计算属性(Computed Properties)和侦听属性(Watchers)。

这两种方式都用于监视数据的变化并执行相应的逻辑,但它们在使用场景和功能上有一些区别。

计算属性 (Computed Properties)

计算属性是 Vue 提供的一种属性,用于在模板中放置逻辑处理,以便计算和返回一个新的响应式属性。

计算属性会缓存计算结果,在依赖的数据未改变时会直接返回缓存的结果,避免不必要的重复计算。

使用场景

  • 当一个属性的值依赖于一个或多个其他属性的值时,可以使用计算属性来计算这个值。
  • 当一个属性的值需要进行复杂的逻辑计算时,可以使用计算属性来简化模板的逻辑。

在模板中可以像访问普通属性一样访问计算属性。

<template>
  <div>
    <p>Original: {{ message }}</p>
    <p>Computed: {{ reversedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('');
    }
  }
};
</script>

侦听属性 (Watchers)

侦听属性允许你在数据变化时执行异步或开销较大的操作。

通过使用侦听属性,你可以监听特定的数据变化并执行自定义的逻辑,比如发起一个网络请求、更新其他数据等。

使用场景

  • 当需要在某个数据变化时执行异步操作或复杂的逻辑时,可以使用侦听属性。
  • 当一个属性变化时需要执行多个逻辑操作时,可以使用侦听属性来分离这些操作。
<template>
  <div>
    <p>Message: {{ message }}</p>
  </div>

</template>

<script>
export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  watch: {
    message(newValue, oldValue) {
      console.log('Message changed:', newValue, oldValue);
      /
/ 在这里执行其他逻辑操作
    }
  }
};
</
script>

源码解析

接着让我们再来深入源码,看看 ComputedWatchers 是如何实现的。

Computed 的工作原理

在 Vue 中,计算属性的工作原理可以概括为以下几个步骤:

  • 创建计算属性时,Vue 内部会生成一个与之相关联的 Watcher 实例。
  • 计算属性的 get 方法会被执行,该方法内部会对计算属性的依赖进行收集。
  • 当计算属性依赖的响应式数据发生变化时,Watcher 实例会通知计算属性的 get 方法重新执行。
  • 计算属性的返回值会被缓存起来,直到其依赖发生变化,才会重新计算。
  • 计算属性的源码实现涉及到在 initState 函数中初始化计算属性并将其代理到实例上,以及在 Watcher 类中处理计算属性的依赖收集和重新计算逻辑。

Computed 的源码解析

计算属性的核心在于定义一个具有 get 方法的函数,该函数在属性被访问时自动执行,返回计算结果。

Vue 内部会创建一个依赖追踪系统,使得计算属性能够自动依赖于其所引用的响应式属性,当这些属性发生变化时,计算属性会重新计算。

计算属性的实现主要在 core/instance/state.js 中,其中的核心逻辑是在 initState 函数中处理的:

function initState(vm{
  // ...

  if (opts.computed) initComputed(vm, opts.computed);

  // ...
}

// 初始化计算属性
function initComputed(vm, computed{
  const watchers = (vm._computedWatchers = Object.create(null));

  for (const key in computed) {
    const userDef = computed[key];
    const getter = typeof userDef === 'function' ? userDef : userDef.get;

    watchers[key] = new Watcher(
      vm,
      getter || noop,
      noop,
      computedWatcherOptions
    );

    // 将计算属性代理到 vm 实例上
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    }
  }
}

// 定义计算属性
export function defineComputed(target, key, userDef{
  const shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef;
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }

  // ...

  Object.defineProperty(target, key, sharedPropertyDefinition);
}

Watchers 的原理

在 Vue 中,侦听属性的工作原理可以概括为以下几个步骤:

  • 创建侦听属性时,也会生成一个相关联的 Watcher 实例。
  • Watcher 实例会监听指定的数据变化,在数据变化时执行回调函数。
  • 当侦听的数据发生变化时,Watcher 实例会通知回调函数执行。
  • 侦听属性的源码实现包括在 initState 函数中初始化侦听属性并创建对应的 Watcher 实例,以及在 Watcher 类中处理侦听属性的更新逻辑。

Watchers 的源码解析

侦听属性的核心在于创建一个 Watcher 实例来监视特定的数据变化,当数据发生变化时,执行相应的回调函数。侦听属性可以用于处理异步操作、复杂逻辑、多属性的变化等场景。

侦听属性的实现也是在 core/instance/state.js 中的 initState 函数中处理:

function initState(vm{
  // ...

  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }

  // ...
}

// 初始化侦听属性
function initWatch(vm, watch{
  for (const key in watch) {
    const handler = watch[key];
    createWatcher(vm, key, handler);
  }
}

// 创建侦听属性的 Watcher
function createWatcher(vm, expOrFn, handler, options{
  return vm.$watch(expOrFn, handler, options);
}

// Vue 实例的 $watch 方法
Vue.prototype.$watch = function (expOrFn, cb, options{
  const vm = this;
  options = options || {};
  const watcher = new Watcher(vm, expOrFn, cb, options);

  if (options.immediate) {
    cb.call(vm, watcher.value);
  }

  return function unwatchFn({
    watcher.teardown();
  };
};

总结

  • 计算属性适合处理简单的逻辑计算,并在模板中使用。
  • 侦听属性适合处理异步操作或复杂的逻辑,并在数据变化时执行特定的操作。

根据具体的需求,可以灵活选择使用计算属性或侦听属性来管理响应式数据的变化。

最后

顺便也给我们的辅导服务打个广告: