大家好,今天文章由团队的 Uncle13 老师提供,题目也是来源于真实的面试。
“computed 和 watch 有哪些区别?”,今天的文章除了介绍两者的区别和使用场景,还会通过源码的剖析,带大家了解具体的实现原理。
以下是正文。
相信 Vue 技术栈的小伙伴都了解,Vue 提供了两种处理响应式数据的方式:计算属性(Computed Properties)和侦听属性(Watchers)。
这两种方式都用于监视数据的变化并执行相应的逻辑,但它们在使用场景和功能上有一些区别。
计算属性是 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>
侦听属性允许你在数据变化时执行异步或开销较大的操作。
通过使用侦听属性,你可以监听特定的数据变化并执行自定义的逻辑,比如发起一个网络请求、更新其他数据等。
<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>
接着让我们再来深入源码,看看 Computed 和 Watchers 是如何实现的。
在 Vue 中,计算属性的工作原理可以概括为以下几个步骤:
计算属性的核心在于定义一个具有 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);
}
在 Vue 中,侦听属性的工作原理可以概括为以下几个步骤:
侦听属性的核心在于创建一个 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();
  };
};
根据具体的需求,可以灵活选择使用计算属性或侦听属性来管理响应式数据的变化。
顺便也给我们的辅导服务打个广告: