前端面试基础知识整理【Day-6】

目录

前言

Vue篇

1.Vue3中的Diff

1.1Diff是什么

1.2为什么要用Diff

1.3Diff用在哪里

2.为什么v-for中的key不建议使用index

3.computed和watch的区别

4.nextTick是什么

5.Vue3的编译优化有哪些

6.v-if和v-for的优先级

Vue生态篇

1.父子组件的生命周期执行顺序

2.组件通信有哪些方式

3.keep-alive是什么

4.Vue-Router的Hash模式和History模式区别

5.MVVM和MVC是什么

6.Pinia和Vuex的区别

代码手写篇

1.简单复现Vue里的reactive


前言

前端面试基础知识整理【Day-1】-CSDN博客

前端面试基础知识整理【Day-2】-CSDN博客

前端面试基础知识整理【Day-3】-CSDN博客

前端面试基础知识整理【Day-4】-CSDN博客

前端面试基础知识整理【Day-5】-CSDN博客

Vue篇

1.Vue3中的Diff

1.1Diff是什么

Diff是一个对比函数,它是一个用来比较两个JavaScript对象(虚拟DOM)差异的函数。

具体来说吗,它比较的是"旧的虚拟DOM树 "和"新的虚拟DOM树 ",它对比结果不是简单的"true / false ",而是一系列的"操作指令 ",它的目标是:"尽可能复用节点,尽可能少移动DOM"

具体算法过程如下:

假设场景:

  • 旧列表(Old):(A,B),C,D,E,(F,G)
  • 新列表(New):(A,B),E,C,D,H,(F,G)

首先,Vue会执行一个"掐头去尾"的操作,系统生成两个指针(头指针尾指针),分别从列表头尾向中间遍历

从头开始比:

  • 指针i从0开始比
  • Old[0]是A,New[0]是A,相同不操作,i++
  • Old[1]是B,New[1]是B,相同不操作,i++
  • Old[2]是C,New[2]是E,不相同,指针停止

从尾开始比:

  • Old[-1]是G,New[-1]是G,相同不操作,i++
  • Old[-2]是F,New[-2]是F,相同不操作,i++
  • Old[-3]是E,New[-3]是H,相同不操作,指针停止

此时,头部(A,B)和尾部(F,G)已经处理完毕,直接去掉,剩下的乱序部分是:

  • Old剩余:[C,D,E]
  • New剩余:[E,C,D,H]

其次,掐头去尾后,发现一种特殊情况,就直接处理:

  1. 新列表比旧列表长:如果旧的遍历完了,新的还有剩余的,说明剩下的都是新增的,直接插入
  2. 旧列表比新列表长:如果新的遍历完了,旧的还有剩,说明剩下的都是不需要的,直接删入

(此时我们的列表既有新增又有移动,所以)

最后,Diff需要处理将[C,D,E]变成[E,C,D,H]的过程:

  1. 遍历旧列表[C,D,E]:C、D、E在Map中都找得到,记下位置,同时生成一个新数组用来记录"新列表中的节点在旧列表中的位置",newArray[5,3,4,0]
  2. 计算最长递增子序列:现在我们有一个位置数组[5,3,4,0],Vue需要找出哪一串节点的位置顺序是没变的,这样这串节点就不用移动,最后找到[3,4]的最长递增子序列是[3,4],这样它们两个不需要移动
  3. 移动节点:Vue倒序遍历新列表的乱序不分,H是0表示新增,创建并插入H,E是5,将E移动到C前面

总结:

Diff算法就是在内存中,用最快的速度找出新旧两个虚拟DOM树之间有什么不同

1.2为什么要用Diff

使用Diff算法的主要原因只有一个:"性能好"

假设没有Diff时,你修改了列表中的一个商品价格,浏览器会执行下面三个步骤:

  1. 操作:浏览器清空当前的整个列表DOM
  2. 重建:浏览器根据新数据,重新创建所有商品的DOM元素
  3. 插入:把所有新元素塞回页面

这样做性能很差,会让页面卡顿、闪烁。

使用Diff算法后,浏览器会执行下面几个步骤:

  1. 生成:Vue在内存中生成一个新的虚拟DOM
  2. 对比:算法发现某个商品的价格改变
  3. 更新:Vue只去操作真实DOM中的那一个文本节点,其它部分完全不动

这样做性能最好

1.3Diff用在哪里

Diff算法并不是时时刻刻都在运行的,它只发生在"组件更新"时,当响应式数据(ref、reactive)发生变化,出发了组件的重新渲染时,Diff算法才会使用

Diff算法在Vue的patch()函数中,这个函数属于Vue的核心渲染源码

Vue的更新粒度是组件级别的,数据变化只会触发该数据所属组件的Diff

2.为什么v-for中的key不建议使用index

v-for 根据key来判断节点是否是同一个,如果使用index作为key会产生如下后果:

  1. 当在数组中插入或删除数据时,所有的节点index都会改变
  2. Vue会以为这些节点全部被改变了,所以会导致整个节点列表重新渲染,造成性能浪费
  3. 除此之外,Vue还可能让节点错误的原地复用,比如输入框中的残留内容被错误的渲染到其它节点中

所以在实践中,推荐使用数据库里的唯一ID作为key值

3.computed和watch的区别

特性 computed watch
侧重点 属性是什么 要做什么事情
缓存 有缓存,依赖不变时,直接返回缓存值,不执行函数 无缓存,每次变化都会执行回调
执行时机 同步,在渲染或读取时计算 异步 / 同步,当数据变化时触发
适用场景 生成一个新数据 执行一些操作
返回值 必须有返回值 不需要返回值

总结:

  • computed是为了得到一个新值
  • wacth是为了数据变化时处理一些附加操作

4.nextTick是什么

nextTick是Vue的一个方法,它接受一个回调函数,将这个回调函数插入到微任务队列中

Vue的DOM更新是异步的,当有多个响应式数据修改后,Vue只记录最后一次修改,当没有数据修改后,DOM更新在微任务中完成,此时nextTick的回调在DOM更新的后面

5.Vue3的编译优化有哪些

Vue3的性能提升表现在编译器在编译阶段就可以分析将文件分类成:"静态文件 "和"动态文件"

随后编译器对动态文件执行以下操作

  1. 对动态文件打上数字标记
  2. 标记分为三类:"只有文字会变的文件"、"只有类名会变的文件"、"只有样式会变的文件"

编译器对静态文件执行以下操作

  1. 对文件静态提升,编译器将它们的创建代码提升到渲染函数之外
  2. Vue3只全局创建一次,渲染时直接复用同一个变量

编译器对事件侦听创建缓存,比如@click="handleClick"这个事件侦听,Vue3会生成一个内联函数包裹它并将它缓存,避免了不必要的组件更新

6.v-if和v-for的优先级

在Vue3中v-if大于v-for,在Vue2中v-for大于v-if

禁止v-if和v-for写到一个节点上,因为if先执行,此时for还没有遍历,所以在v-if里访问不到v-for的变量,会报错

如果v-if的变量确实依赖于v-for里面的变量,那么有两种解决方案:

第一种,使用computed计算变量,在JS层筛选数据:

javascript 复制代码
<script setup>
import { ref, computed } from 'vue'

const list = ref([
  { id: 1, name: 'A', isShow: true },
  { id: 2, name: 'B', isShow: false },
  { id: 3, name: 'C', isShow: true }
])

// 1. JS 层过滤,生成一个新的数组
const visibleList = computed(() => {
  return list.value.filter(item => item.isShow)
})
</script>

<template>
  <ul>
    <li v-for="item in visibleList" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

第二种,使用<template>标签将v-for提到上层:

javascript 复制代码
<template>
  <ul>
    <template v-for="item in list" :key="item.id">
      
      <li v-if="item.isShow">
        {{ item.name }}
      </li>
      
    </template>
  </ul>
</template>

Vue生态篇

1.父子组件的生命周期执行顺序

一个原则:"由外向内初始化,由内向外挂载"

挂载阶段的执行顺序:

  1. 父组件setup
  2. 父组件onBeforeMount
  3. 子组件setup
  4. 子组件onBeforeMount
  5. 子组件onMounted
  6. 父组件onMounted

更新阶段(父组件传递给子组件的props更新或父组件重新渲染时)的执行顺序:

  1. 父组件onBeforeUpdate
  2. 子组件onObeforeUpdate
  3. 子组件onUpdated
  4. 父组件onUpdated

卸载 / 销毁阶段的执行顺序:

  1. 父组件onBeforeUnmount
  2. 子组件onBeforeUnmount
  3. 子组件onBeforeUnmounted
  4. 父组件onUnmounted

总结:"总是以父组件开始,再以父组件结束"

2.组件通信有哪些方式

  1. Props:父传子
  2. $attrs:父传子
  3. Emits:子传父
  4. Ref / Expose:子传父
  5. v-mode:双向绑定
  6. Provide / Inject:祖先传子孙
  7. Pinia、Vuex库:全局组件
  8. EventBus / mitt:全局组件

3.keep-alive是什么

keep-alive是Vue的一个内置组件:

  • 作用:缓存组件实例,避免组件在切换时被销毁和重建
  • 场景:Tab切换、多页面跳转
  • 生命周期:被缓存的组件可以使用两个生命周期钩子:"onActivated "(激活时)和"onDeactivated"(停用时)

原理是LRU缓存算法:

  • keep-alive有一个max属性,限制缓存数量
  • 当设置缓存max为2,并且缓存了[A,B]两个组件时
  • 访问A命中,A变成"最近使用的",顺序变为[B,A]
  • 访问新组件C时,缓存已满,算法会淘汰最久没用的B,存入C,序列变成[A,C]

4.Vue-Router的Hash模式和History模式区别

特点 Hash模式 History模式
URL格式 带#号 不带#号,干净好看
底层原理 基于window.onhashchange事件 基于HTML5 History API
请求行为 #后面的内容不会发送给服务器 完整的URL会发送给服务器
兼容性 极好 需要IE10+
部署配置 不需要服务器特殊配置 必须配置服务器(Nginx / Apache)

History模式不管什么URL都会向服务器发送请求,例如当访问一个"site.com/home"URL,Vue会向服务器请求/home这个文件,如果服务器没有这个文件,就会报404。此时就需要服务器配置try_files,不管请求什么路径,如果找不到这个文件,统统返回index.html

5.MVVM和MVC是什么

MVVM:

  • M(Model):数据模型(后端传来的JSON数据)
  • V(View):视图(HTML页面)
  • VM (ViewModel):视图模型(Vue实例),负责协调M和V,VM可以自动同步M和V的状态

MVC:

  • M(Model):数据模型
  • V(View):视图(HTML页面)
  • C(Controller):负责协调M和V,Controller需要手动修改M或者V,不能自动更新、同步M和V的状态

6.Pinia和Vuex的区别

Pinia是Vue官方推荐的"下一代Vuex",它有如下优点:

  1. 更简单的API(去掉了Mutation):Pinia只有state、Getter、Action,直接在Action里修改State即可
  2. TypeScript支持更好
  3. PInia可以创建多个独立的Store,它们之间是独立的,按需引入
  4. Pinia体积比Vuex更小

代码手写篇

1.简单复现Vue里的reactive

javascript 复制代码
const handler = {
  get(target, key, receiver) {
    console.log("正在读取" , key);
    const result = Reflect.get(target, key, receiver);
    return typeof result === 'object' && result !== null ? reactive(result) : result;
  },
  set(target, key, value, receiver) {
    console.log("正在设置" , key);
    const result = Reflect.set(target, key, value, receiver);
    return result;
  }
}

function reactive(target) {
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  return new Proxy(target, handler);
}
const user = reactive({
  name: "张三",
  age: 18
});


const company = reactive({
  name: "Tech Corp",
  info: {
    address: {
      city: "Beijing",
      street: "Road 1"
    },
    employees: 100
  }
});

console.log("--- 深度测试 ---");

const myCity = company.info.address.city;
console.log("--- 修改深度属性 ---");

company.info.employees = 200;

结果:

相关推荐
星火开发设计1 小时前
关联式容器:set 与 multiset 的有序存储
java·开发语言·前端·c++·算法
追随者永远是胜利者1 小时前
(LeetCode-Hot100)72. 编辑距离
java·算法·leetcode·职场和发展·go
未来龙皇小蓝1 小时前
RBAC前端架构-06:使用localstorage及Vuex用户信息存储逻辑
前端·vue.js
BUG集结者2 小时前
【Navigation3】结合ViewModel(二)
前端
用户80806181436932 小时前
JavaScript 异步编程完全指南:从入门到精通
前端
凯里欧文4272 小时前
CSS Grid 案例
前端·css
天若有情6732 小时前
Vuex 的核心作用深度解析:构建高效可维护的 Vue 应用状态管理体系
前端·javascript·vue.js·vuex
jimy12 小时前
撇除猪肉腥味调料
职场和发展·程序员创富
哆啦A梦15882 小时前
Vue3魔法手册 作者 张天禹 015_插槽
前端·vue.js·typescript·vue3