面试-Vue

39 阅读8分钟

响应式原理

Vue2

  • 基于Object.defineProperty的数据劫持

    • 通过Object.defineProperty来拦截对象属性的gettersetter从而实现对数据的监听
    • Vue在初始化时,会调用observe()方法遍历data中的所有属性,并使用Object.defineProperty进行数据劫持
    • 每个属性都会被添加gettersetter
    • 访问数据时,getter负责依赖收集(Dep)
    • 修改数据时,setter触发依赖更新(通知Watcher)
  • 依赖收集与派发更新

    • 通过发布-订阅模式来实现依赖收集和视图更新

      • Dep(依赖收集)

        • 构造器里会有一个存储依赖的数组subs
        • 还有添加依赖的addSub方法,和通知更新的notify方法
      • Watcher(观察者)

        • 有一个更新视图的update方法
      • 每创建一个响应式数据,就会创建一个dep实例

      • 每当有其他地方引用这个响应式数据,就会创建一个watcher实例,并且dep实例会调用addSub方法,将watcher实例传入,收集依赖

      • 响应式数据更新时,dep实例就会调用notify方法,遍历所有收集的依赖(watcher实例),并且调用watcher实例的update方法进行视图更新

  • 缺陷

    • 深层嵌套对象性能开销大:递归遍历data
    • 对象属性的新增、删除无法检测
    • 直接通过索引修改数组无法检测(通过重写数组方法实现响应式)

Vue3

  • 基于Proxy对响应式数据进行代理,代理的是一个对象,所以不管是ref还是reactive创建一个响应式数据都是返回一个对象

  • 优势

    • 代理整个对象,不需要像Object.defineProoerty对对象的每个属性进行监听
    • 除了get、set,还有has、delete等10几种,能监听到对象属性的新增、删除,通过索引修改数组元素
  • 原理和Vue2类似,也是发布订阅模式,只是具体细节不同

    • 同样是创建响应式数据时(如:通过ref),创建一个dep实例
    • 每当有其他地方引用这个响应式数据时,调用dep里的track方法收集依赖
    • 当响应式数据改变时,调用trigger方法,trigger方法里再调用notify进行更新

双向绑定

概念

  • 数据发生变化时视图自动更新,用户操作视图时,数据也会自动更新,数据和视图能自动保持同步
  • Vue的双向绑定主要通过v-model指令实现的,底层原理依赖于事件监听和Vue的响应式系统

原理

  • 《响应式原理》
  • 事件监听(input,change)

应用

  • 表单输入
  • 自定义组件

diff算法

概念

  • diff算法是一种用于比较两个树形结构差异的算法。
  • 目标是找出两个树之间的最小变化量,从而减少操作次数,提高更新DOM的效率。
  • 在Vue中,diff算法被用于比较新旧DOM树的差异,然后将这些差异应用于真实DOM上。

比较流程

只会进行同层比较

  • 如果新节点不存在,旧节点存在,则销毁旧节点

  • 如果新节点存在,旧节点不存在,则创建元素

  • 否则,比对新旧节点

    • 如果节点相同,直接复用

    • 如果节点类型不同,则创建元素

    • 如果类型相同

      • 如果是文本节点,直接更新内容
      • 如果是其他节点,则更新属性
      • 再去比较子节点

Vue3

  • 判断节点是否相同,相同则直接return

  • 如果新旧节点类型不同,使用unmount直接销毁旧节点(及其子节点)

  • 根据新节点类型进行判断

    • 文本节点

      • 类型不同,直接创建
      • 类型相同,更新内容
    • 注释节点

    • 静态节点

    • 组件节点

    • 元素节点

      • 类型不同,直接创建

      • 类型相同

        • 根据patchFlag,进行判断

          • 如果是更改属性(class、style),直接更新
          • 如果是文本节点,更新内容
        • 再对子节点,做一次完全的diff(双端比较)

特点

  • 同层比较

    • Vue的diff算法只会比较同一层级的节点,不会跨层比较
    • 因为跨层比较的节点通常意味着整个子树的替换,开销较大
  • 双端比较

    • 会从新旧两颗虚拟DOM树的头部和尾部开始比较,尝试找出相同的节点
  • 节点复用

    • 如果节点相同,直接返回
    • 如果节点类型相同,会复用节点,只更新变化的属性,而不是创建新的节点
  • key属性:为节点添加唯一的key,能帮助diff算法快速找到相同的节点

diff算法的优化

  • 双端比较算法优化
  • 静态标记

虚拟DOM

概念

虚拟 DOM (Virtual DOM) 是一种编程概念,它用一个 JavaScript 对象来描述 DOM 树,Vue、React都使用了虚拟DOM优化页面渲染性能。

优势

  • 当我们需要频繁更新页面时,频繁操作DOM会导致页面回流、重绘,从而影响页面性能

  • 如果有虚拟DOM,我们修改数据时,Vue会先更新虚拟DOM树,然后将新旧DOM树进行比较(diff算法),找出差异部分,将差异部分更新到真实DOM上

    • 使用对象来描述真实DOM,每当数据变化时,Vue通过Diff算法比较新旧虚拟DOM,找出最小变更,并更新真实DOM

      • 同层级比较
      • 使用key作为唯一标识,避免不必要的DOM操作,提高性能
      • 节点复用:如果节点类型相同,会尝试复用节点,只更新变化的属性

key的作用

nexttick

概念

  • nexttick是Vue中的一个异步方法,用于在DOM更新完成后执行对应的回调函数

Vue中DOM更新机制

  • Vue的响应式更新是异步的,数据变化后,不会立即更新DOM
  • 而是会在下一次事件循环,合并多次数据更新,再执行DOM操作
  • 避免频繁更新DOM,减少回流和重绘,提升页面性能

实现原理

  • Promise,MutationObserver,setTimeout
  • const callbacks = [] // 存储回调函数
    let pending = false // 标记是否正在等待执行function flushCallbacks() {
      pending = false
      const cpoies = callbacks.slice(0)
      callbacks.length = 0
      cpoies.forEach(cb => cb())
    }
    ​
    function nexttick(cb) {
      callbacks.push(cb)
      
      if (!pending) {
        pending = true
        Promise.resolve().then(flushCallbacks)
      }
    }
    

使用场景

  • 新增/删除元素,获取列表高度

自定义指令

概念

  • 自定义指令是 Vue 提供的一个指令的扩展功能,允许开发者创建自己的指令
  • 这些指令可以绑定在DOM元素上,并在元素的生命周期钩子上执行特定的操作
  • 一般用于需要直接操作 DOM 的场景

补充

  • Vue自己有一些内置指令:v-on,v-bind,v-if,v-show,v-for,v-model

  • 注册自定义指令分为:全局注册,局部注册

  • 注册自定义指令,可以获取到

    • 被绑定的元素
    • 绑定信息:值、参数、修饰符、...
    • 元素的生命周期钩子:created、mounted、updated、unmounted

场景

  • 权限控制:v-hasPermi
  • 表单验证
  • 高亮指定字符串

生命周期

概念

  • Vue生命周期是指Vue实例/组件从创建到销毁的这个过程
  • 这个过程中,提供了一系列生命周期钩子函数,以便在不同阶段处理对应的逻辑(数据初始化,操作DOM,销毁监听器)

钩子函数

  • Vue2

    • beforeCreate

      • 数据初始化之前,此时无法访问data、computed、methods
      • 用途:一般不会使用
    • created

      • 实例创建完成,可以访问到实例的data、computed、methods,模板还未编译
      • 用途:初始化数据,异步请求
    • beforeMount

      • 模板编译完成,DOM还未挂载
      • 用途:操作DOM前的工作
    • mounted

      • 实例已经挂载到DOM上,可以访问到真实DOM
      • 用途:echarts、antv/g6图表库的初始化工作
    • beforeUpdate

      • 数据更新之后,视图重新渲染之前
      • 用途:
    • updated

      • 数据更新后,视图重新渲染后
      • 用途:访问更新后的DOM
    • beforeDestroy

      • 实例销毁之前
      • 用途:清除定时器,清除事件监听
    • destroyed

      • 实例销毁后
      • 用途:
  • Vue3

    • onBeforeMount
    • onMounted
    • onBeforeUpdate
    • onUpdated
    • onBeforeUnmount
    • onUnmounted

组件通信

概念

  • 在Vue中,组件是构建用户页面的基本单元。
  • 组件之间通常需要相互传递数据。

通信方式

  • 父向子:props、$ref
  • 子向父:emit
  • 祖先后代:provide、inject
  • eventBus
  • Vue官方状态管理库:Vuex、Pinia

watch、computed、watchEffect

概念

  • 用来监听组件实例中的响应式数据
  • 当数据变化时,会触发相应的回调函数
  • 非常适合处理需要在数据变化时,执行异步操作或复杂逻辑的场景

用法

  • 触发watch后,可获取到原来的数据和变化后的数据
  • 可以通过配置deep属性,深度监听对象,数组
  • 通过配置immediate属性,在监听开始时,立即执行回调函数

应用场景

  • 表单校验
  • 复用页面,监听路由变化

对比

  • computed

    • 返回计算的值,避免过多表达式,不适合做异步操作
    • 有缓存机制,无论页面调用几次,都只会执行一次,只有当响应式数据变化时才会重新计算
    • 场景:根据优惠/折扣计算价格,翻转分割字符串
  • watchEffect

    • 不用显示指定依赖的数据属性,自动推断需要监听的依赖

Vue2和Vue3区别

  • OptionsAPI、CompositionAPI

    • OptionsAPI

      • 使用选项来组织逻辑代码
    • CompositionAPI

      • 写法更加灵活
      • 更好的代码组织,便于维护和重用
  • 响应式系统

    • Object.defineProperty

      • 设计目的是为了对对象的属性进行更精细化的控制(可写、可枚举),vue2利用它的get、set实现了响应式

      • 缺点

        • 无法监听对象属性的新增、删除
        • 无法监听通过索引修改数组
    • Proxy

      • 对整个对象进行代理,监听、拦截整个对象
      • 提供多种捕获器(get、set、has、delete)
  • 编译优化

    • Vue3 对于模板编译进行了优化,支持静态提升
  • 更好的TS支持

    • 提供更好的类型推导,类型检查
  • 其他

    • 生命周期
    • 模板不需要根标签
    • Suspense、Teleport传送DOM结构到指定元素

hooks

概念

  • hooks源于React,将状态和方法组合在一起,方便代码复用的一种方式。
  • Vue3中,引入类似于hooks的CompositionAPI,让开发者能更好地复用逻辑
  • 很好地解决了Vue2中OptionsAPI使用mixin,导致代码来源难以追溯的问题

相关库

Vue-router

概念

  • 是Vue官方的路由管理库
  • 通过不同的URL访问不同的组件,从而构建单页面应用(SPA)
  • SPA通过JavaScript动态更新页面内容,而无需加载整个页面

核心功能

  • 路由映射:通过路由配置,将URL映射到Vue组件

  • 导航:支持页面之间的跳转,维护浏览器历史记录

  • 路由守卫:提供路由跳转前后的钩子,方便权限验证,数据加载顺序等

  • 嵌套路由:定义子路由,适合复杂的页面布局

  • 动态路由:用户个人中心

  • history模式和hash模式

    • history

      • 利用原生的historyAPI,监听popstate事件

      • 部署时,需进行配置

        • 用户访问example.com/about,Vue Router 处理路由,渲染 About.vue 组件

        • 如果用户刷新,浏览器会把这个路径当做静态资源,导致404

        • 解决方法:配置Nginx

          • 先尝试查找路径对应的静态资源
          • 如果找到,则返回
          • 未找到,则返回index.html,然后让Vue Router处理
          • server {
                listen 80;
                server_name example.com;
            ​
                location / {
                    root /usr/share/nginx/html;
                    index index.html;
                    try_files $uri /index.html; # 关键:如果找不到文件,就返回 index.html
                }
            ​
                error_page 404 /index.html;  # 处理 404
            }
            
    • hash

      • 利用url上的hash当做路由,监听hashchange事件(router4.x版本改为history同样的方式)
      • 路由跳转时,直接修改hash
      • hash值变化时,浏览器不会向服务器发送请求,路由完全交由前端处理
      • 不利于SEO

路由组件

  • 渲染匹配的组件
  • 创建链接导航

Vuex

概念

  • Vue的全局状态管理库,采用单一状态树,所用状态都存储在store对象里

核心概念

  • State

    • 存储应用的全局状态
  • Getters

    • state的计算属性
  • Mutations

    • 修改state的方法
  • Actions

    • 处理异步任务
  • Modules

    • 将store拆分为多个模块

Pinia

概念

  • Vue3官方推荐的新一代状态管理库

核心概念

  • State
  • Getters
  • Actions

优点

  • 更符合CompositionAPI的写法

  • 更好的TS支持

  • 体积更小,性能更好

  • 更简单的API

    • 不需要Mutations去修改state,可以直接修改,更加灵活
    • 每个store都是一个独立的模块,无需Modules