Vue作为前端常用框架之一,在前端领域有着重要的地位,本文介绍了一些常见知识点和面试考点,当作对自己前端生涯中的一些总结。
MVVM
- Model:业务逻辑和数据保存检索的部分
- View:用户界面,展示数据
- ViewModel:连接视图和模型之间的桥梁,vue采用双向数据绑定实现
生命周期详解
| 选项式API | 组合式API |
|---|---|
| beforeCreate:实例初始化之后,数据观测和事件配置之前 | setup |
| created:实例创建完成,数据观测和事件配置完成 | |
| beforeMount:挂载开始之前 | onBeforeMount |
| mounted:实例挂载完成后,可执行节点的DOM操作 | onMounted |
| beforeUpdate:数据更新调用,发生在虚拟DOMpatch和重新渲染之前 | onBeforeUpdate |
| updated:DOM重新渲染后调用 | onUpdated |
| beforeDestroy/beforeUnmount:卸载之前调用,可以执行清除操作 | onBeforeUnmount |
| destroyed/mounted:卸载完成 | onUnmounted |
响应式原理
- vue2采用Object.defineProperty,但每次只能拦截单个属性的读写,无法直接监听数组变化
- vue3采用Proxy,可以拦截整个对象的操作,可以直接监听数组变化
- 通过数据劫持和发布订阅模式实现(vue3):使用Proxy对响应式对象进行监听和更新;访问对象属性时,触发get,消息订阅器Dep收集Watcher;Observer监听所有属性,更新对象属性时,触发set,Dep通知Watcher执行更新操作;指令解析器Compile,当Watcher执行更新操作时,执行对应的更新函数更新视图
- 手写简易版reactive
let activeEffect = null;
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => effect.update());
}
}
let targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
function effect(effect) {
new Watcher(effect);
}
class Watcher {
constructor(effect) {
this.effect = effect;
this.update();
}
update() {
activeEffect = this;
this.effect();
activeEffect = null;
}
}
const reactive = (target) => {
const collectionHandlers = {
get(target, key) {
const dep = getDep(target, key);
dep.depend();
return Reflect.get(target, key);
},
set(target, key, value) {
const dep = getDep(target, key);
const result = Reflect.set(target, key, value);
dep.notify();
return result;
},
};
return new Proxy(target, collectionHandlers);
};
// test
const state = reactive({
count: 1,
});
const render = () => {
document.body.innerHTML = `<h1>${state.count}</h1>`;
};
effect(() => {
render();
});
document.body.addEventListener(
"click",
() => {
state.count++;
},
false
);
编译阶段
Vue编译本质就是将模板转换为渲染函数,大致过程为:
- 将模板字符串解析成AST
- 分析模板,检测不需要更改的部分,将其优化成静态内容,这些内容在重新渲染时不会重新生成,vue3对静态节点进行了编译优化,将静态节点移动到render函数外部缓存,渲染时直接复用
- 将AST转换为渲染函数
运行阶段
执行渲染函数得到VNode,实现页面渲染,每次有数据变化,会重新调用渲染函数,生成新的VNode,通过对比新旧VNode,提高页面渲染效率,vue3中VNode会根据节点的patch flag只对需要重新渲染的数据进行更新。
在此过程中,diff算法起到了至关重要的作用,下面介绍一下Vue2和Vue3的diff算法:
- vue2双端Diff算法
- 新旧两组子节点四个端点之间分别进行比较,如果找到可复用节点,进行patch,移动节点
- 如果四个端点对比都不满足,则遍历旧组子节点,寻找与新组拥有相同key值的元素,找到了进行patch,未找到创建新节点
- 循环结束后,处理剩余节点,新增或删除节点
- vue3快速Diff算法
- 预处理:处理新旧组子节点中相同的前置节点和后置节点
- 剩余节点的处理:只有新组子节点有剩余,新增节点;只有老组子节点有剩余,删除节点;新老组子节点都有剩余,构建source数组,存放新组子节点在老组中的索引,-1代表需要新增的节点,删除无索引的旧节点,通过最长递增子序列移动旧节点,减少了移动次数,增加了移动效率
组件通信
- vue2:props/$emit、provide/inject、EventBus、vuex
- vue3:defineProps/defineEmits、provide/inject、mitt、pinia
侦听器(vue3)
- watch需要显式指定监听数据源,默认初始化不执行,获取变化前后的值
- watchEffect会自动收集回调函数中的响应式依赖,初始化立即执行一次,只能获取当前值
nextTick
- 可以获取更新后的DOM,vue采取异步更新策略,会把同一个事件循环中发生的所有更新保存在队列中,这一策略导致对数据的修改不能立即体现在DOM上;
- nextTick原理是将回调函数推入队列的末尾,确保在DOM更新完成后执行回调,相当于一个微任务
路由
- route获取当前路由信息
- router管理路由,可以进行跳转或者挂载守卫