前端面试题(React 与 Vue)

目录

一、React

函数组件

Fiber架构

组件重新渲染

组件通信

为什么不能在if中使用hook

useEffect与useLayoutEffect区别

性能优化hooks

受控组件与非受控组件

redux与zustand区别

二、Vue

vue2与vue3区别

生命周期

computed与watch区别

v-if与v-show区别

v-model如何实现

ref与reactive区别

组件通信

key的作用,为什么不建议用index作为key

虚拟dom与diff算法

响应式原理与proxy

对keep-alive的理解

vue与react中diff算法区别

vue与react区别


一、React

函数组件

函数式编程摒弃传统的面向对象思想,比起指令式编程,函数式编程更强调程序执行的结果,而不是过程。

一个简单的函数如何实现复杂的功能?

React将HTML、js全部写在函数组件中,函数组件的返回值就是要渲染的UI,这种设计更注重于js逻辑,而无需像vue一样提供各种模板语法。

还有就是状态,需要逻辑状态改变来触发页面的更新。函数组件是一个纯函数,通过函数自身的执行去渲染界面,本身无法存留状态,所以采用hooks的方式为组件提供状态,既不破坏纯函数的特性,又能在状态变化时让函数组件重新执行并更新页面。

还有生命周期,React函数组件摒弃了这个概念,取而代之的是使用useEffect在特定时候让函数组件重新执行并渲染。

React更愿意在函数上做文章,用函数作为组件的基础,用纯函数简单的重复执行来代替复杂的视图更新流程,Hooks也是函数,useState触发函数组件的执行,并作为纯函数执行的不同输入,useEffect将函数组件中的所有副作用从函数中隔离出去,自定义hooks也是函数。React看起来如此复杂的框架,归根结底只是通过函数的执行去渲染出视图而已,这也是React如此简洁和优雅的原因。

Fiber架构

Fiber 架构实现可中断的异步渲染,解决了传统同步渲染阻塞主线程的问题。

Fiber 节点结构

每个组件对应一个 Fiber 节点,构成链表树。节点包含组件类型、状态、副作用标记,节点指针等。

主要流程

状态更新触发组件重新渲染,启动更新流程。

Render 渲染阶段构建 Fiber 树

利用时间切片将渲染任务拆分为微任务,通过调度器按优先级异步执行,渲染过程可被高优先级任务中断。

从根节点开始采用深度优先遍历,递归处理每个 Fiber 节点,逐步构建 Fiber 链表树。

diff 算法对比新旧虚拟 DOM,复用 key 和类型相同的节点,标记新增 / 更新 / 删除等副作用,处理子节点时需要两次遍历,第一次遍历进行匹配和复用,第二次遍历进行增删和移动。

当节点没有子节点时,自底向上收集副作用,将子节点的副作用合并到父节点,最终形成从根节点到子节点的副作用链表。

每处理完一个 Fiber 节点,就检查时间片是否耗尽,如果耗尽就暂停任务并保存进度,等浏览器空闲时继续执行渲染任务。

Commit 提交阶段更新真实 DOM

遍历副作用链表,批量执行 DOM 操作,完成视图渲染。

构建完成的 Fiber 链表树替换当前的 Fiber 链表树,保证视图连续性。它们通过一个指针相关联,以便复用节点数据。

时间切片/调度器:设置任务优先级,将任务拆分为微任务,每次执行完微任务后,检查是否有高优先级任务插队执行,然后通过 MessageChannel 实现异步调度,利用浏览器空闲时间执行。

简要概述:

Render渲染阶段用时间切片将渲染任务拆分为微任务来执行,用深度优先遍历构建 Fiber 链表树,Diff 算法对比新旧 Fiber 节点,然后标记副作用,自底向上收集副作用形成副作用链表,每处理完一个 Fiber 节点就检查是否有高优先级任务,如果有就暂停渲染并保存进度,等浏览器空闲时继续执行渲染任务。

​​Commit提交阶段遍历副作用链表,批量执行 DOM 操作,完成视图渲染。

组件重新渲染

  • 状态更新
  • 父组件传递来的props变化
  • 强制更新ForceUpdate
  • 父组件重新渲染
  • 上下文Context变化

组件通信

  • 父传子props
  • 子传父回调函数
  • 跨层级context API
  • 复杂状态管理使用redux或zustand
  • 事件总线event bus可跨层级但破坏单向数据流
  • ref使用子组件

为什么不能在if中使用hook

React顶层调用规则,要求每次渲染时hooks的调用顺序必须完全一致。

React使用链表来管理hooks状态,每个hook根据它在链表中的位置来标识。每次渲染时,react会严格记录hooks的调用顺序,组件重新渲染时react会按上一次渲染时hooks的执行顺序来匹配对应状态,如果使用if等语句,可能会导致hooks在链表中的顺序发生变化,使得react的无法关联正确的状态。

useEffect与useLayoutEffect区别

useEffect 的设计是用来执行一段和当前渲染无关的副作用代码,同时解决了和生命周期相关的一些问题,通过依赖项数组的不同,实现不同的功能,使用return来清理副作用。

useLayoutEffect 执行需同步执行的逻辑,如DOM测量或修改。

useEffect 浏览器绘制后​​异步执行,不阻塞渲染​;useLayoutEffect:浏览器绘制前同步执行,阻塞渲染​

性能优化hooks

useCallback 与 useMemo 缓存

useCallback 缓存一个方法,useMemo 缓存一个计算后的值,当它们的依赖无变化时就不会重新声明或计算

因为函数组件通过重复执行的方式不停的执行自身渲染视图,在函数组件内部声明的方法每次都会被重新声明,函数组件中写的逻辑每次都会被重新执行,可能会带来一定的浪费,极端情况下会影响页面性能。

React.memo

防止子组件不必要的渲染,当父组件重新渲染时,如果父组件传递给子组件的props没有变化,则子组件不会重新渲染。

但这并不意味着需要滥用这些性能优化方法,应当在真的存在问题的时候再去考虑用它解决。比如真的在函数组件里写了一个cpu计算超级复杂的逻辑,频繁执行确实会影响组件的渲染,此时再考虑用 useMemo 去缓存计算结果。

受控组件与非受控组件

React中的组件根据是否受React控制可分为受控组件和非受控组件。

受控组件的值由React组件的状态控制,由用户输入触发React状态更新,然后触发重新渲染并更新DOM;

非受控组件的值不受React控制,由自身管理状态,通常使用ref来获取输入值,由用户输入直接更新DOM。

一般推荐使用受控组件来实现表单,数据由React组件负责处理。

redux与zustand区别

Zustand提供了轻量级状态管理,API简洁易用;Redux 相比之下是重量级状态管理,适用于大型或复杂项目,使用起来没有Zustand简洁。

状态模型:

在Zustand中直接通过设置set来更新状态; Redux则使用action和reducer来管理状态。

更新机制:

Redux 的更新机制会触发全局检查,依赖开发者用手动优化来避免无效渲染;而 Zustand 仅通知关联字段的组件更新,实现真正按需渲染。

语法差异:

Redux中是不可变数据,也就是当更新状态时不直接修改原数据​​;而Zustand是可变语法,允许直接修改状态属性​​,底层库自动处理不可变状态转换。


二、Vue

vue2与vue3区别

vue2是选项式API,vue3是组合式API,提供了set up语法糖,还有响应式API:Ref和reactive,解决了vue2选项式API的逻辑碎片化问题。

Vue2使用object.defineProperty来实现对数据对象属性的getter和setter进行拦截,从而实现双向数据绑定及响应式更新。

Vue3使用ES6中的proxy来代理整个数据对象,提供完整的响应式机制,能更好的处理数组变化并监听深层对象属性变化。

生命周期钩子做了调整,Vue3中的模板支持多个根节点,且更好的支持ts。

生命周期

Vue2选项式API生命周期钩子:

beforeCreate → created → beforeMount → mounted → beforeUpdate → updated → beforeUnmount → unmounted

Vue3组合式API在 setup() 中使用生命周期钩子,前缀为 on,beforeCreate 和 created 被 setup() 替代。

setup是组合式API的入口,用于定义响应式数据、方法和生命周期钩子,setup中没有this。

computed与watch区别

computed的用于派生数据,也就是依赖其他值计算的结果,且具有缓存机制,只有依赖值变化,才会重新计算,如果没有变化则直接返回缓存的值;而且计算属性本身就是响应式,改变会自动触发依赖更新。

watch一般用于监听特定的数据变化并执行副作用,比如异步请求或操作DOM等,支持深度监听和立即执行,需要显式监听。

WatchEffect可以自动追踪依赖,响应式数据变化时自动重新执行。

副作用

副作用是指在代码执行过程中,对外部环境产生的可观察变化,比如修改全局变量、操作DOM、发起网络请求、定时器操作、打印日志等。

纯函数:相同的输入必有相同的输出。

v-if与v-show区别

v-if条件渲染,元素不存在于DOM中,切换时触发组件的创建或销毁,适用于条件不经常变化或者很少改变的场景。

v-show通过CSS来控制隐藏和显示,速始终存在于DOM中,适用于需要频繁切换显示状态的场景。

v-if切换成本高,v-show初始渲染成本高。

v-model如何实现

​​ v-model 的主要功能是在​​表单输入或​​自定义 Vue 组件​​上创建​​双向数据绑定​​,它同时负责数据的读取和数据的写入。

在原生表单元素上的实现原理:

html 复制代码
<input v-model="username">

等效于以下写法:

html 复制代码
<input :value="username" @input="username = $event.target.value">

在自定义组件上的实现原理:

html 复制代码
<custom-input v-model="username">

等效于以下写法:

html 复制代码
<custom-input  :model-value="username"  @update:model-value="username = $event">

子组件通过 props 接收 modelValue,触发 update:modelValue 事件,也可以用 emits 触发更新。

组件内部实现:

javascript 复制代码
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
  handleInput(e) {
    this.$emit('update:modelValue', e.target.value)
  }
}

ref与reactive区别

ref可以用于基本数据类型,也可以用于引用类型,而reactive只能用于对象。

使用ref需要加value,如果将ref用于对象,则会自动转化为reactive包装的。

如果直接替换一整个reactive对象,会失去响应式;直接解构也会失去响应式,需要使用toRefs。

组件通信

  • props/自定义事件,v-model,父子间通信
  • provide/inject,跨层级通信
  • 事件总线,任意组件间通信
  • 使用pinia管理状态
  • 父组件使用子组件的ref
  • 路由参数,用于页面间传递数据
  • 插槽,普通插槽是父组件传数据给子组件,作用域插槽是子组件数据传递给父组件
  • 浏览器本地存储

对keep-alive的理解

keep-alive用于缓存组件实例,避免重复销毁和渲染。

被包裹的组件会触发特殊生命周期,切换时执行 deactivated 钩子,不会被销毁,再次激活时执行 activated。使用LRU算法(最近最少使用)自动清理缓存。

​​可以指定缓存规则和最大缓存实例数。

key的作用,为什么不建议用index作为key

key的作用是帮助diff算法识别节点的身份,通过复用相同key的节点,来优化渲染性能。

如果用index作为key,如果列表中的某项被删除,则后面的元素index全都会改变,可能导致原本可以复用的节点被重新创建。

如果列表项有输入框内容,用index会导致状态错乱。

正确做法是使用能够唯一标识的值作为key,比如数据库ID。

虚拟DOM与diff算法

虚拟DOM是什么?

虚拟DOM是真实DOM的轻量级表示,它通过js的对象数来描述真实DOM的结构和属性。

为什么要有虚拟DOM?

因为直接操作DOM的代价比较高,涉及到浏览器的回流和重绘,频繁操作会有性能问题。因此虚拟DOM作为缓冲层,通过diff算法对比新旧虚拟DOM树,找到最小化的更新操作,批量更新真实DOM,减少直接操作次数。

局限性:内存开销,简单场景可能性能不足。

Diff算法用来对比新旧虚拟DOM树的差异,找到最小化的更新操作,应用到真实DOM。

Diff算法仅比较同一层级的节点,不跨层级对比,若节点类型不同,则销毁旧子树并创建新子树。如果类型相同,则会对比并更新属性,然后递归处理子节点。

在列表渲染中,通过key标识节点身份,如果没有key就会按索引顺序对比。

vue与react中diff算法区别

列表对比策略与节点移动优化不同

Vue3采用双端对比+LIS算法优化,React采用单向递归+key机制。

Vue3从列表头尾同步开始对比,预处理相同节点,对于剩下的乱序节点,通过最长递归子序列算法计算最小移动路径。

React采用两轮遍历,首轮按索引顺序更新可复用节点,次轮处理剩余节点的新增、删除或移动,因为fiber节点为单向链表。

响应式原理与proxy

vue3的响应式原理基于js的proxy对象来实现。

当使用reactive包装一个普通对象时,vue会创建一个代理对象Proxy,该对象会拦截所有对原始对象的访问和修改。

当访问对象的属性时,Proxy的get拦截器会被触发,然后执行依赖收集,将当前正在执行的代码,也就是副作用函数与该属性建立联系。

如果访问的是对象,vue会递归的将其转换为响应式对象。

当修改对象的属性时,Proxy的set拦截器会被触发,vue会检查新值与原值是否相同,如果不相同vue会触发更新,通知所有依赖该属性的副作用函数重新执行。

只有实际被使用的属性才会建立依赖。

ref将基本值包装到一个对象中,这个对象有一个.value属性。

访问.value的值时会触发getter,进行依赖收集;修改.value的值时会触发setter,检查值是否变化然后触发更新。

vue与react区别

相同点:都采用组件化,都使用虚拟DOM和diff算法,都实现数据驱动视图。都有父子组件传参数据状态管理和路由等。

核心理念不同

vue的核心理念是降低前端开发门槛,是一个灵活易用的渐进式框架,提供双向数据绑定。

React的核心思想是声明是声明式编程和单向数据流。

组件化差异

Vue将HTML CSS和js写在同一个文件中,就是.vue文件,使用的是组合式API。

React则把HTML和CSS全部写进js中,使用.jsx文件,使用的是函数组件。

Diff算法不同

Vue使用双端对比以及LIS算法,而react是单向递归与fiber架构。

Vue内置了很多黑魔法,而react则是社区生态丰富。

Vue上手简单,React更灵活。


相关推荐
AlienZHOU3 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc6 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
君顾16 小时前
上海24小时自助健身房系统开发实战指南:从架构设计到落地部署
java·开发语言·健身房
香菜TTT7 小时前
大模型上下文协议(MCP):AI 应用的“USB-C”接口技术
开发语言·人工智能·经验分享
aramae7 小时前
模拟实现strlen()函数 (C语言)
c语言·开发语言·后端
计算机魔术师8 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen8 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
jimy18 小时前
readelf 查看“派生类”的vtable符号
开发语言·c++
IT_陈寒8 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow9 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端