面试题答案
一键面试优化计算属性
- 优化思路:
- 防抖与节流:对于计算属性依赖的频繁变化数据源,若计算属性计算成本较高,可对数据源变化进行防抖或节流处理。防抖是指在一定时间内,若数据源多次变化,只在最后一次变化后执行计算属性的重新计算;节流是指在一定时间间隔内,无论数据源变化多少次,只允许执行一次计算属性的重新计算。
- 缓存计算结果:对于一些不依赖响应式数据变化的计算属性,可以缓存其计算结果,避免重复计算。例如使用
WeakMap
来缓存计算结果,在计算属性依赖的数据没有变化时,直接返回缓存结果。
- 示例代码:
- 防抖处理计算属性:
<template> <div> <input v-model="inputValue"> <p>{{ debouncedComputed }}</p> </div> </template> <script> export default { data() { return { inputValue: '', timer: null }; }, computed: { debouncedComputed() { clearTimeout(this.timer); return new Promise((resolve) => { this.timer = setTimeout(() => { resolve(this.inputValue.toUpperCase()); }, 300); }); } } }; </script>
- 缓存计算结果:
<template> <div> <p>{{ cachedComputed }}</p> </div> </template> <script> const cache = new WeakMap(); export default { data() { return { fixedData: 'constant value' }; }, computed: { cachedComputed() { if (!cache.has(this)) { const result = this.fixedData.split('').reverse().join(''); cache.set(this, result); } return cache.get(this); } } }; </script>
优化侦听器
- 优化思路:
- 减少不必要的侦听:确保只对真正需要响应变化的属性进行侦听。避免过度侦听一些不会影响业务逻辑的属性变化,以减少不必要的性能开销。
- 使用深度侦听策略:对于对象或数组类型的数据变化,若要精确捕捉内部深层次的变化,应合理使用深度侦听,但注意深度侦听会带来性能损耗,所以只在必要时开启。
- 合并侦听器:如果多个侦听器逻辑类似,可以考虑合并成一个,减少重复代码和性能开销。
- 示例代码:
- 避免过度侦听:
<template> <div> <input v-model="user.name"> <input v-model="user.age"> </div> </template> <script> export default { data() { return { user: { name: '', age: 0 } }; }, watch: { // 只侦听对业务逻辑重要的属性 'user.name': { handler(newVal, oldVal) { // 处理名字变化的业务逻辑 }, immediate: true } } }; </script>
- 深度侦听:
<template> <div> <input v-model="user.address.city"> </div> </template> <script> export default { data() { return { user: { address: { city: '' } } }; }, watch: { user: { handler(newVal, oldVal) { // 处理地址变化的业务逻辑 }, deep: true, immediate: true } } }; </script>
跨组件通信替代方案
- 优化思路:
- 使用 Vuex 或 Pinia:对于大型应用,状态管理库如 Vuex(Vue 2)或 Pinia(Vue 3)可以集中管理共享状态,避免多层嵌套组件之间繁琐的传值。通过将共享数据存放在状态管理库中,各个组件可以方便地获取和修改数据,并且状态的变化具有可追踪性。
- 事件总线:对于兄弟组件或跨层级不太深的组件通信,可以使用事件总线。创建一个空的 Vue 实例作为事件总线,在需要通信的组件中通过事件总线触发和监听事件来传递数据。但事件总线在大型应用中可能导致代码难以维护,所以适用于较小规模的组件通信场景。
- Provide / Inject:对于祖先 - 后代组件通信,
provide
和inject
可以实现数据的向下传递,而不需要通过中间多层组件层层传递。provide
用于在祖先组件中提供数据,inject
用于后代组件中注入数据。不过要注意,provide
和inject
传递的数据不是响应式的,若要实现响应式,需使用ref
或reactive
包装数据。
- 示例代码:
- 使用 Vuex(Vue 2 示例,Vue 3 中类似概念是 Pinia):
- store.js:
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: { sharedData: 'initial value' }, mutations: { updateSharedData(state, newData) { state.sharedData = newData; } } }); export default store;
- 组件中使用:
<template> <div> <p>{{ $store.state.sharedData }}</p> <button @click="updateData">Update Data</button> </div> </template> <script> export default { methods: { updateData() { this.$store.commit('updateSharedData', 'new value'); } } }; </script>
- 事件总线:
- main.js:
import Vue from 'vue'; import App from './App.vue'; Vue.prototype.$eventBus = new Vue(); new Vue({ render: h => h(App) }).$mount('#app');
- 发送数据组件:
<template> <div> <button @click="sendData">Send Data</button> </div> </template> <script> export default { methods: { sendData() { this.$eventBus.$emit('data - sent', 'Hello from component 1'); } } }; </script>
- 接收数据组件:
<template> <div> <p>{{ receivedData }}</p> </div> </template> <script> export default { data() { return { receivedData: '' }; }, mounted() { this.$eventBus.$on('data - sent', (data) => { this.receivedData = data; }); } }; </script>
- Provide / Inject:
- 祖先组件:
<template> <div> <child - component></child - component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; import { ref } from 'vue'; export default { components: { ChildComponent }, setup() { const sharedData = ref('data from ancestor'); return { sharedData }; }, provide() { return { sharedData: this.sharedData }; } }; </script>
- 后代组件:
<template> <div> <p>{{ injectedData }}</p> </div> </template> <script> export default { inject: ['sharedData'], setup(props, { inject }) { const injectedData = inject('sharedData'); return { injectedData }; } }; </script>
- 使用 Vuex(Vue 2 示例,Vue 3 中类似概念是 Pinia):