面试题答案
一键面试Vue的响应式原理
- 数据劫持:Vue使用
Object.defineProperty()
方法来劫持对象的属性访问和赋值操作。当一个Vue实例创建时,Vue会遍历data对象中的所有属性,并使用Object.defineProperty()
将它们转化为getter
和setter
。在getter
中收集依赖,在setter
中触发依赖更新。例如:
let data = { name: 'John' };
let vm = {};
Object.defineProperty(vm, 'name', {
get() {
// 收集依赖,例如添加订阅者到依赖数组
return data.name;
},
set(newValue) {
if (newValue!== data.name) {
data.name = newValue;
// 触发依赖更新,通知订阅者更新视图
}
}
});
- 依赖收集与发布订阅模式:每个组件实例都有一个对应的
Watcher
实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。当依赖发生变化时,Watcher
会收到通知,从而触发组件重新渲染。具体流程如下:- 初始化阶段:组件渲染时,会读取data中的属性,此时
getter
方法被调用,将当前Watcher
添加到该属性的依赖列表中。 - 更新阶段:当属性值通过
setter
被改变时,会遍历该属性的依赖列表,通知所有Watcher
进行更新,Watcher
会触发组件重新渲染视图。
- 初始化阶段:组件渲染时,会读取data中的属性,此时
Vuex中基于响应式原理实现状态的响应式更新
- Vuex的状态存储:Vuex使用单一状态树,将整个应用的状态存储在一个对象中。这个状态对象其实就是一个普通的JavaScript对象,Vue通过响应式原理将其变成响应式数据。
- 依赖收集与更新:当组件从Vuex的
store
中读取状态时,会触发状态属性的getter
,从而收集依赖(即当前组件的Watcher
)。当通过mutation
(Vuex中修改状态的唯一方式)修改状态时,会触发状态属性的setter
,进而通知依赖的Watcher
,使得相关组件重新渲染。例如:
// Vuex store示例
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
}
});
当组件使用store.state.count
时,会收集依赖,当调用store.commit('increment')
时,state.count
的setter
被触发,通知依赖的组件更新。
手动修改state中属性视图未更新的原因及排查解决方法
- 原因
- 未通过mutation修改:Vuex规定只能通过
mutation
来修改state
,直接手动修改state
属性不会触发Vue的响应式更新机制,因为没有经过setter
。 - 对象或数组的变更检测问题:Vue无法检测对象属性的新增或删除,以及直接通过索引修改数组。例如:
- 未通过mutation修改:Vuex规定只能通过
// 直接添加对象属性
let obj = { a: 1 };
Vue.set(obj, 'b', 2); // 正确方式
obj.b = 2; // 错误方式,不会触发响应式更新
// 直接通过索引修改数组
let arr = [1, 2, 3];
arr[0] = 4; // 错误方式,不会触发响应式更新
arr.splice(0, 1, 4); // 正确方式
- 排查方法
- 检查修改方式:确认是否是通过
mutation
来修改state
。查看代码中修改state
的地方,是否使用了store.commit
调用mutation
。 - 确认数据结构操作:检查是否存在直接添加或删除对象属性,或通过索引直接修改数组的情况。
- 检查修改方式:确认是否是通过
- 解决方法
- 使用mutation修改:将手动修改
state
的操作封装到mutation
中,通过store.commit
调用。 - 使用Vue提供的方法:对于对象新增属性,使用
Vue.set
或this.$set
(在组件内);对于数组修改,使用splice
等Vue能检测到变化的方法。例如:
- 使用mutation修改:将手动修改
// 在mutation中
mutations: {
addProperty(state) {
Vue.set(state.obj, 'newProp', 'value');
}
}