前端Vue应知应会

56 阅读2分钟

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编译本质就是将模板转换为渲染函数,大致过程为:

  1. 将模板字符串解析成AST
  2. 分析模板,检测不需要更改的部分,将其优化成静态内容,这些内容在重新渲染时不会重新生成,vue3对静态节点进行了编译优化,将静态节点移动到render函数外部缓存,渲染时直接复用
  3. 将AST转换为渲染函数

运行阶段

执行渲染函数得到VNode,实现页面渲染,每次有数据变化,会重新调用渲染函数,生成新的VNode,通过对比新旧VNode,提高页面渲染效率,vue3中VNode会根据节点的patch flag只对需要重新渲染的数据进行更新。
在此过程中,diff算法起到了至关重要的作用,下面介绍一下Vue2和Vue3的diff算法:

  1. vue2双端Diff算法
  • 新旧两组子节点四个端点之间分别进行比较,如果找到可复用节点,进行patch,移动节点
  • 如果四个端点对比都不满足,则遍历旧组子节点,寻找与新组拥有相同key值的元素,找到了进行patch,未找到创建新节点
  • 循环结束后,处理剩余节点,新增或删除节点
  1. 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管理路由,可以进行跳转或者挂载守卫