希yin

47 阅读21分钟

一:vite和webpack对比,有哪些优势

Vite 对比 Webpack 的核心优势是开发环境下的极速启动与热更新,其本质是利用浏览器原生 ES 模块(ESM)跳过打包环节,而 Webpack 需先打包所有模块再启动服务。

具体优势可分为三点: 1. 开发启动速度快:无需预打包,直接基于 ESM 加载文件,项目越大,Vite 启动速度优势越明显(Webpack 启动需等待全量打包)。 2. 热更新(HMR)响应快:只更新修改的模块,无需重新编译整个项目,修改代码后浏览器能瞬时响应(Webpack HMR 常需等待模块重新打包)。 3. 配置更简洁:内置对 Vue、React 等框架的优化配置,开箱即用场景多,无需像 Webpack 那样编写大量 loader 和 plugin 配置。 需要注意的是,Vite 在生产环境仍会进行打包(基于 Rollup),两者最终构建产物的性能差异不大,核心差距集中在开发体验上。

二:webpack的模块联邦是怎么实现的

Webpack模块联邦(Module Federation)的核心原理是通过共享依赖和代码拆分实现微前端架构的独立部署与运行时共享‌。

  1. ‌模块联邦的核心概念‌

Webpack 5 引入的模块联邦是一种‌微前端解决方案‌,允许不同应用在‌运行时‌动态共享模块(如组件、库等),而无需重新打包。其核心目标包括: ‌独立部署‌:各子应用可独立开发、构建和部署。 ‌依赖共享‌:避免重复加载公共库(如 React、Vue)。 ‌动态加载‌:运行时按需加载远程模块

Webpack 模块联邦的核心是让多个独立构建的 Webpack 项目(称为“应用”)能互相暴露和引用对方的模块,实现跨应用代码共享,本质是通过 runtime 动态加载远程模块并处理依赖。

其实现需遵循 3 个关键步骤,核心依赖  ModuleFederationPlugin  插件: 1. 暴露模块(Remote 端):在需要共享代码的项目中,通过插件配置  exposes ,指定要对外暴露的模块(如组件、工具函数),并定义自身的“应用名”( name )和入口文件( filename )。 2. 引用模块(Host 端):在需要使用远程模块的项目中,通过插件配置  remotes ,声明要引入的远程应用(格式为“应用名@远程入口文件地址”),如  { app2: "app2@http://localhost:3002/remoteEntry.js" } 。 3. 动态加载与运行:Host 端运行时,会先加载 Remote 端的  remoteEntry.js (包含模块映射表),再根据映射动态拉取所需模块,Webpack 会自动处理模块的依赖关系,让远程模块像本地模块一样使用

三:react hooks的state是怎么保存的?

React Hooks的state是绑定在当前组件的Fiber节点上的,每个组件实例都有独立的Fiber节点,state就存储在该节点的 memoizedState 属性中。

Hooks的调用顺序至关重要,React会通过**“链表”结构**按调用顺序记录每一个Hook(如useState),后续渲染时再按相同顺序从链表中读取对应的state值,确保state不会错乱。

四:redux的state

Redux的state是存储在单一的“状态树”(Single State Tree) 中的,整个应用的所有状态都集中在这一个JavaScript对象里,且该状态树是只读的,只能通过触发Action来间接修改。

Redux通过Reducer函数处理状态更新:当Action被Dispatch后,Reducer会接收当前的state和Action,根据Action的类型计算并返回一个全新的state对象(而非修改原state),最终由Redux将新state更新到状态树中。

五:不同tab页如何通信?不借助服务端呢?

不同Tab页(同源)不借助服务端通信,核心是利用浏览器共享的存储或通信API,主流方案有3种:

  • localStorage/sessionStorage:一个Tab修改存储,另一个Tab通过监听 storage 事件接收变化。
  • Broadcast Channel API:专门用于同源页面间通信,创建频道后直接发送/接收消息,比storage更高效。
  • SharedWorker:创建一个后台共享线程,所有Tab页连接该线程,通过线程中转消息,适合复杂数据交互

六:websocket连接需要什么条件?http是websocket吗?

  1. WebSocket连接的核心条件
  • 协议支持:客户端与服务器均需支持WebSocket协议(RFC 6455),常用实现如浏览器原生 WebSocket 对象、Node.js的 ws 库。
  • 握手基础:依赖HTTP协议完成初始握手,客户端发送含 Upgrade: websocket 和 Connection: Upgrade 头的HTTP请求,服务器返回 101 Switching Protocols 响应后切换为WebSocket协议。
  • 网络环境:需允许WebSocket默认端口(80对应ws://,443对应wss://)的网络通信,无防火墙或代理拦截。
  1. HTTP不是WebSocket

二者是完全不同的协议,核心区别在于通信模式:

  • HTTP是单向、无状态的请求-响应模式,客户端需主动发请求,服务器才会回复,无法主动推送数据。
  • WebSocket是双向、持久化的全双工模式,连接建立后双方可随时互相发送数据,无需重复握手。

七、如何做心跳检测?

  1. WebSocket心跳检测实现方案

核心是通过定时发送“空消息”或“特定指令”,确认连接是否存活,避免被网络中间件(如网关、路由器)判定为闲置连接而断开。

1. 客户端主动发送心跳:客户端创建定时器(如 setInterval ),每隔固定时间(如30秒)调用 ws.send('ping') 发送心跳包。 2. 服务器响应心跳:服务器收到“ping”后,立即回复“pong”作为确认。 3. 超时判定断开:客户端设置超时计时器,若超过指定时间(如60秒)未收到“pong”,则判定连接断开,触发重连逻辑。

八:幽灵依赖是什么意思?如何解决?

项目的依赖树中可能出现所谓的“幽灵依赖”,这是指那些在项目的package.json文件中没有直接声明,但是因为被其他依赖引入而存在项目中的依赖。这种依赖的主要问题在于:

难以追踪:因为这些依赖没有直接声明,在尝试了解项目使用了哪些包及其版本时,这会造成混淆。

版本冲突:如果两个模块分别依赖同一个包的不同版本,而npm或yarn解决方案导致某些模块实际使用的版本与其依赖的版本不一致,可能会造成意料之外的问题。

尽管yarn通过yarn.lock锁定了版本以及通过一定的策略减少了此类问题,但仍然不能完全避免。

依赖分身(Dependency Duplication)

举例

-node_module/a > -node_module/a/node_module/b@1.0.0 > -node_module/c > -node_module/c/node_module/b@2.0.0

这时,依赖a和c分别依赖b的不同版本。

不论此刻提升@1.0.0的版本到根目录层级还是提升@2.0.0的版本到根目录层级,都会出现一个问题,将出现一个重复的依赖拷贝,这个依赖拷贝就叫作:依赖分身。

依赖分身指的是在项目的node_modules目录中,同一个依赖包存在多个不同版本的情况。这种情况可能发生的原因包括:

不同的依赖要求:项目中不同的模块可能依赖同一个包的不同版本。

扁平化机制的失败:虽然yarn和较新版本的npm尝试通过依赖扁平化(将依赖尽可能安装在顶层node_modules下)来减少重复依赖的问题,但并不是所有情况都能完全扁平化,特别是当存在不兼容版本时。

这会导致以下几个问题:

增加项目大小:同一个包的多个版本会占用更多的磁盘空间。

运行时不确定性:可能导致难以预料的行为,因为不同的代码部分可能会使用同一个包的不同版本。

解决方案和建议

对于“幽灵依赖”,建议定期使用npm ls或yarn list来审查项目的依赖树,确保了解所有实际使用的依赖及其来源。

避免“依赖分身”的一个方法是在更新或安装新依赖时认真评估版本要求,尽可能地统一依赖的版本。对于npm,可以使用npm dedupe尝试减少重复的依赖。

使用现代化的工具和方法(如使用最新版本的npm和yarn以及合理配置package.json)可以在一定程度上缓解这些问题。

PNPM(Performant NPM)是一种快速且节省磁盘空间的包管理工具。相较于其他包管理器如NPM和Yarn,PNPM通过独特的存储机制和链接技术解决了许多常见的问题。以下是PNPM如何避免这些问题以及其关键技术的详细介绍。

PNPM Store 是PNPM用来存储所有包的中央仓库。它的主要特点和优势包括:

去重存储:PNPM Store是全局的,这意味着所有项目共用同一个包存储。当你在不同的项目中安装同一个包时,PNPM只会在Store中存储一次,避免了重复下载和存储。

高效缓存:PNPM Store充当了包的缓存库,安装过的包会被缓存下来,以便下次安装时直接从缓存中读取,提高了安装速度。

硬链接的工作原理如下: 链接而非复制:在传统的包管理器中,每个项目都会独立存储其依赖的包,而PNPM通过硬链接将这些包从PNPM Store链接到项目的node_modules目录,而不是复制一份新的。这种方式极大地节省了磁盘空间。

共享相同文件:硬链接使得多个项目可以共享同一个物理文件,修改其中一个链接的内容会影响到所有其他链接,但在日常开发中这不会导致问题,因为包文件通常是只读的。

符号链接 也是PNPM用来管理依赖关系的一种方式。 它的主要特点包括: 指向目标路径:符号链接类似于快捷方式,它们指向目标文件或目录。PNPM使用符号链接在项目的node_modules目录中创建包的引用,而这些符号链接指向PNPM Store中的实际包。

灵活性:符号链接允许在不同的目录结构之间灵活地引用和访问包文件,进一步简化了依赖管理。

pnpm如何解决常见问题 通过上述技术,PNPM有效地解决了许多传统包管理器中存在的问题:

磁盘空间浪费:传统的NPM和Yarn会在每个项目中独立存储依赖包,导致大量的磁盘空间浪费。PNPM通过去重存储和硬链接技术,大幅减少了磁盘空间的占用。

安装速度慢:由于PNPM Store缓存了所有下载过的包,后续的安装可以直接从缓存中读取,大大提高了安装速度。

依赖冲突:PNPM通过符号链接和独特的依赖树结构,避免了传统node_modules目录下可能出现的依赖冲突问题。

包一致性:PNPM确保所有项目使用的依赖包版本一致,从而避免了由于不同版本的包引发的兼容性问题。

总结 PNPM通过引入PNPM Store、硬链接和符号链接等技术,从根本上解决了传统包管理器在磁盘空间、安装速度和依赖管理等方面的问题。这使得PNPM成为一种高效、可靠且节省资源的包管理工具,为开发者提供了更好的开发体验。如果你还没有尝试过PNPM,不妨一试,它可能会让你的开发工作变得更加高效和愉快。

解决方法 1. 显式声明依赖:将实际使用到的幽灵依赖,手动添加到 package.json 的 dependencies (生产依赖)或 devDependencies (开发依赖)中,锁定版本。 ​ 2. 使用严格依赖模式:部分包管理器支持禁止使用未声明依赖(如npm的 --strict-peer-deps ,pnpm默认隔离间接依赖),从源头阻断幽灵依赖的使用。 ​ 3. 定期审计依赖:通过 npm ls <依赖名> 或 yarn why <依赖名> 排查项目中未声明但被使用的依赖,及时清理或显式声明。

九:pnpm是如何解决依赖的?

pnpm主要通过以下方式解决依赖问题:

  • 依赖存储与共享:pnpm将依赖存储在内容可寻址的存储中,不同项目可以共享同一版本的依赖。当依赖项有新版本时,只会将不同的文件添加到存储中,而不是克隆整个依赖,从而节省磁盘空间。 ​
  • 硬链接与符号链接:安装依赖时,pnpm会将依赖包的文件硬链接到项目的 node_modules 目录,而不是复制文件,进一步节省空间并加快安装速度。对于项目的直接依赖项,pnpm默认使用符号链接将其添加到 node_modules 目录的根目录,避免了传统扁平目录结构中源码可直接访问和修改间接依赖的问题,增强了依赖管理的安全性。 ​
  • 依赖解析与安装阶段:pnpm分三个阶段执行安装,首先进行依赖解析,识别并获取仓库中没有的依赖;然后计算 node_modules 目录结构;最后将所有以前安装过的依赖项从存储区中获取并链接到 node_modules ,这种方式比传统的安装过程更快。 ​
  • 对等依赖处理:对于有对等依赖的情况,pnpm会根据不同的依赖集创建多个硬链接,以确保每个依赖集都能正确解析和使用。例如,如果 foo@1.0.0 有两个对等依赖 bar@^1 和 baz@^1 ,且不同的父包依赖不同版本的 baz ,则pnpm会为 foo@1.0.0 创建多个硬链接,每个硬链接对应一组不同的依赖。

十:软链接和硬链接的区别?

软链接是文件的“快捷方式”,依赖原文件;硬链接是文件的“别名”,与原文件共享数据和inode,具体区别如下:

  1. 本质与依赖关系
  • 软链接:独立文件,仅存储原文件的路径引用(绝对/相对路径),依赖原文件存在。若原文件删除/移动,软链接会失效(“断链”)。 ​
  • 硬链接:与原文件共享同一个inode(文件系统索引节点,记录文件数据位置、权限等),无依赖关系。删除原文件,硬链接仍可正常访问文件数据(仅当所有硬链接+原文件都删除,数据才会从磁盘清除)。
  1. 跨文件系统支持
  • 软链接:可跨不同文件系统(如从本地磁盘链接到U盘文件),因为仅记录路径。 ​
  • 硬链接:不可跨文件系统,因为inode是单个文件系统的唯一标识,不同文件系统的inode编号无关联。
  1. 适用对象与特性
  • 软链接:可指向文件或目录,创建时会占用新的inode(自身作为独立文件)。 ​
  • 硬链接:仅能指向文件(无法链接目录,避免文件系统循环),不占用新inode(仅增加文件的“引用计数”)。

十一:requestAnimationFrame和requestIdleCallbak的区别?

两者均为浏览器异步调度API,但设计目标不同——requestAnimationFrame用于同步屏幕刷新的视觉渲染任务,requestIdleCallback用于利用浏览器空闲时间执行低优先级非渲染任务,具体区别如下:

  1. 调度时机与触发频率
  • requestAnimationFrame(RAF):与屏幕刷新率同步(通常60Hz,约16.67ms触发一次),在浏览器重绘(repaint)前执行。触发时机固定,确保任务与视觉刷新步调一致,避免卡顿。 ​
  • requestIdleCallback(RIC):仅在浏览器空闲时(如页面加载完成、用户无交互、渲染任务结束后)触发,触发频率不固定(空闲时间可能为0~50ms),空闲时间不足时会延迟执行。
  1. 核心用途与优先级
  • RAF:高优先级,用于视觉相关任务,如动画帧更新、DOM元素位置调整、Canvas绘制等。确保动画流畅(60fps),避免掉帧。 ​
  • RIC:低优先级,用于非紧急任务,如日志上报、数据预加载、缓存清理等。不影响页面渲染和用户交互,空闲时才执行,避免抢占主线程资源。
  1. 执行限制与特性
  • RAF:有固定执行周期,即使页面隐藏(如切换标签),触发频率会降低(部分浏览器暂停),但不会完全停止;可通过 cancelAnimationFrame 取消。 ​
  • RIC:有最长执行时间限制(默认50ms),避免占用过多空闲时间导致后续高优先级任务延迟;页面隐藏时可能不触发,且浏览器对其支持度略低于RAF(如Safari需兼容处理);可通过 cancelIdleCallback 取消。

十二:如何监听一个元素的宽度变化?如果有

监听元素宽度变化可通过 ResizeObserver(推荐)、窗口resize事件(兼容旧场景)、MutationObserver(间接监听样式/结构变更)

十三:如何监听几千个元素的宽度变化?

监听数千个元素宽度变化,核心是避免为每个元素单独绑定监听,而是利用事件委托或批量检测方案,平衡性能与准确性。

你提的问题很关键,直接单个绑定会导致严重的性能开销,比如大量内存占用和频繁触发回调,必须从方案设计上优化。

以下是三种主流方案的对比,可根据需求选择。 image.png

  • 首选 ResizeObserver:性能最好,浏览器原生支持
  • 大量元素时:结合防抖、节流、虚拟观察等优化技术
  • 考虑兼容性:如需支持旧浏览器,可使用 polyfill 或回退到定时检测方案
  • 按需监听:只监听需要的元素,及时取消监听

十四:可以监听到浏览器的重排事件吗?

监听数千个元素宽度变化,核心是避免为每个元素单独绑定监听,而是利用事件委托或批量检测方案,平衡性能与准确性。

直接单个绑定会导致严重的性能开销,比如大量内存占用和频繁触发回调,必须从方案设计上优化。

image.png

十五:如何实现响应式布局?

1. 设置视口(Viewport)

首先需在 HTML 的 <head> 中添加视口元标签,确保移动端浏览器正确缩放页面:

2. 媒体查询

通过 @media 规则在不同屏幕尺寸下应用不同样式,是响应式布局的核心技术。

3. 使用流式布局(Fluid Layout)

用相对单位替代固定单位(如 px),使元素尺寸随屏幕变化:

  • 百分比(%) :宽度、高度等基于父元素的比例设置。

    css

    .container {
      width: 100%; /* 占父元素宽度的100% */
      max-width: 1200px; /* 限制最大宽度,避免桌面端过宽 */
      margin: 0 auto; /* 居中 */
    }
    
  • rem/em:基于根元素(html)或父元素字体大小的相对单位,方便整体缩放。

    css

    html {
      font-size: 16px; /* 1rem = 16px */
    }
    .box {
      width: 10rem; /* 160px */
    }
    
  • vw/vh:基于视口宽度 / 高度的单位(1vw = 视口宽度的 1%)。

    css

    .title {
      font-size: 5vw; /* 字体大小随屏幕宽度变化 */
    }
    
  1. 弹性布局
  • Flexbox: 适合一维布局
  • CSS Grid: 适合二维布局

 框架工具(可选)

5.使用成熟的 CSS 框架可简化响应式开发,如:

  • Bootstrap:提供预定义的栅格系统(如 col-sm-6col-md-4)和断点。
  • Tailwind CSS:通过类名直接控制响应式样式(如 md:flexlg:w-1/3)。

核心原则总结:

移动优先:先设计移动端布局,再通过媒体查询扩展到桌面端(推荐 min-width 递增写法)。 相对单位:优先使用 %remvw 等,避免固定 px。 灵活布局:结合 Flexbox/Grid 实现自适应排列。 按需加载:针对不同设备优化资源(如图片、内容)。

十六:px2rem工作原理,如何让某个元素不转换?

实现思路

  1. 使用JavaScript动态计算根元素的字体大小
  2. 通过CSS将px单位转换为rem单位
  3. 提供不转换的类名来排除特定元素
1. 特殊注释标记(推荐)

大多数 px2rem 工具(如 postcss-pxtorem)支持通过注释忽略转换,常用语法:

  • 单个属性忽略:在属性后添加 /* px */ 注释。
  • 整段代码忽略:用 /* pxtorem-ignore */ 和 /* pxtorem-ignore-end */ 包裹。
/* 单个属性不转换 */
.box {
  width: 100px; /* px */ /* 保持 100px,不转换为 rem */
  height: 50px; /* 会被转换为 rem(如 50/16=3.125rem) */
}

/* 整段代码不转换 */
/* pxtorem-ignore */
.title {
  font-size: 24px; /* 保持 24px */
  margin: 10px; /* 保持 10px */
}
/* pxtorem-ignore-end */
2. 使用大写 PX 或 Px

部分工具(如 postcss-px-to-viewport 或自定义配置的 px2rem)会忽略大写的 PX 单位,仅转换小写 px

3. 配置工具的忽略规则(全局设置)

在 px2rem 工具的配置中,可指定不需要转换的文件、类名或属性,以 postcss-pxtorem 为例:

// postcss.config.js
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 16,
      propList: ['*'], // 需要转换的属性(* 表示所有)
      selectorBlackList: ['ignore-'], // 类名前缀为 ignore- 的元素不转换
      // 例如:class="ignore-box" 的元素,其 px 不被转换
    }
  }
}

十七:使用JavaScript动态计算根元素的字体大小

如果我们的UI给出的设计稿是375px,然后我们切换到iphone6下面直接给视图设置357px,先不进行rem转换,那么宽度肯定是刚好占满屏幕。

我们再就行rem转换,375px被转换成3.75rem,那么这个3.75rem要刚好等于屏幕的宽,发现没有等于屏幕的宽。那要怎么才能等于屏幕的宽呢?

1 先解决如何让375px也就是3.75rem占满屏幕

我们需要3.75rem在渲染的时候被底层换算成375px,那么就可以刚好占满屏幕宽度。

3.75rem * font-size=375px,那么font-size=100px。

十八:如何控制并发请求 当前端应用需要同时发送大量请求时,如果不加以控制,可能会导致以下问题:

1. 服务器过载:大量的并发请求可能导致服务器资源耗尽,影响服务器的响应速度,甚至导致服务器崩溃。 2. 用户体验下降:过多的请求可能导致页面卡顿、加载缓慢,影响用户的操作体验。 3. 资源浪费:未加控制的并发请求可能导致网络资源浪费,增加服务器的负担。

1. 使用队列控制并发请求

通过使用队列来管理请求,可以有效地控制并发请求数量。当请求数量超过设定的最大并发数时,多余的请求将被放入队列中等待执行。

2. 使用 Promise 控制并发请求

通过使用 Promise.all 和 Promise.race,可以实现对并发请求的控制。这种方法可以在任何时刻都保持最大数量的并发请求,而不需要等待整个批次完成。

3. 使用 axios 拦截器控制并发请求

通过使用 axios 的请求拦截器和响应拦截器,可以实现对并发请求的控制。这种方法可以在请求发送前和响应接收后进行拦截,从而控制并发请求数量。

  • 请求去重要加过期时间:不然用户刷新页面后,旧的缓存请求可能还在,导致数据过时。

  • 失败重试别无脑重试:比如用户删除数据,第一次失败可能是权限问题,重试10次也没用,应该提示用户“删除失败,请检查权限”。

  • 别让请求“堵死”页面:如果请求卡住了,页面应该给提示(比如“网络开小差了,点击重试”),而不是干等。

  1. 控制节奏:别让浏览器/服务器“堵车”,用并发控制函数限制同时发的数量。

  2. 避免重复:用缓存记录已发的请求,别让用户点一下按钮发10次同样的请求。

  3. 兜底处理:失败了能重试,卡住了能提示,让用户知道“发生了啥”。