Skip to content

12场景题

约 6122 字大约 20 分钟

八股前端

2025-06-17

  1. BigInt js自带
  2. Decimal 库

浮点数精度

  1. 转换成整形
  2. 转换成字符串
  3. decimal库

大文件上传

  1. 切片上传:切片取名:hash+index,后端组合切片,切片时主线程卡顿:web-worker多线程切片。上传完成后通知后端合并切片
  2. 断点续传:选择文件后先请求后端是否有相同文件已上传一部分
  3. 断开重传:
  4. 资源嗅探:切片完后将blob存储到IndexedDB,下次用户进来后嗅探一下是否有未上传完的切片

截图

  1. canvans
  2. puppeteer
  3. html2canvas

移动端适配

  1. 不同端使用不同的网址
  2. 根据不同端加载不同的css
  3. 使用响应式(媒体查询)

水印功能

  1. 明水印(肉眼可见) 使用Canvas或css设置中背景图片水印。 svg动态水印
  2. 暗水印(肉眼不可见) 将水印内容写入到到二进制文件,读取时从文件中读取

静态资源失效

  1. 图片 占位图或out,图片失效时显示 重试、上报
  2. sss 重要的样式使用html内联的写法。
  3. cdn 本地备份,动态切换.
  4. 字体 降低字体(兜底).

图片加载性能优化

  1. 懒加载 在html中将图片的src属性更改为自定义的属性,这样浏览器遇到这个属性时就不会像默认的属性进行处理,当需要加载时将自定义属性的值赋值给src属性

获取到图片的dom,添加监听滚动事件,判断自身与视口的距离,达到一定距离就加载图片 这种方法的缺点是:不停的监听滚动事件,即使已经加载了图片,还是不断地触发事件,性能开销大 IntersectionObserver观察dom元素可视口的交叉区域,每次视口进入dom的显示区域都会调用回调函数。当图片加载后取消观察。

  1. 上拉加载和下拉刷新 监听滚动事件如果新的视口和原视口位置有一定的距离就请求数据并添加到数据列表中 监听触摸事件,下划的矩离与原距离有一定距离就请空教据列表并将请求的数据添加的到表中 同时也要实现节流,防抖

页面加载优化

代码优化

  • 减少HTTP请求:合并文件、使用雪碧图、压缩资源。
  • 优化代码结构:避免重复代码,使用函数和组件提高复用性。
  • 懒加载:延迟加载非首屏资源,如图片、视频、组件。

资源优化

  • 压缩资源:使用Gzip等压缩算法减小文件体积。
  • 缓存资源:利用浏览器缓存,减少重复下载。
  • 使用CDN:加速资源加载。

渲染优化

  • 减少重排和重绘:避免频繁操作DOM,使用DocumentFragment或虚拟DOM。
  • 使用异步加载:如asyncdefer加载脚本,不影响页面渲染。
  • 服务端渲染(SSR):减少首屏加载时间,提升用户体验。

预加载和懒加载的区别

预加载

  • 定义:在页面加载时或用户操作前,提前加载可能需要的资源。
  • 优点:减少用户等待时间,提升体验。
  • 缺点:增加初始加载时间,可能加载无用资源。

懒加载

  • 定义:延迟加载非首屏或暂时不需要的资源,在需要时再加载。
  • 优点:减少初始加载时间,优化性能。
  • 缺点:可能增加用户等待时间,需要合理控制加载时机。

在window对象上绑定数据的问题

  1. 命名冲实 后绑定的重名数据系把前面的覆盖
  2. 安全风险 任何人可以获取和修改。
  3. 性能问题, 占用的存如果不解绑也不会清除
  4. 全局污染 任何人都可以获取到也可以修改

解决:模块化、命名空间、IIFE、开启严格模式、qiankun代理沙箱

实现sleep

基于时间戳的实现

开始时获取当前时间戳,将当前时间戳加上目标时间获得结束时间戳 开启while循环,每次循环获取当前时间戳,当前时间戳大于等于目标时间戳时返回

缺点: 程序一直在执行,程序不会让线程休眠的,而且所有任务都会暂停,一直执行循环 在sleep过程中程序处于假死状态,不会执行其他任务的

基于promise实现

创建一个新的promise,在promise中执行settimeout定时器

如何判断是否是数组

有以下几种方法可以判断一个值是否为数组:

Array.isArray()方法

  • 最常用和推荐的方法,直接判断一个值是否为数组。
  • 示例:Array.isArray(arr)

Object.prototype.toString.call()

  • 利用Object的原型方法toString,通过call改变上下文,获取对象的类型信息。
  • 示例:Object.prototype.toString.call(value) === '[object Array]'

原型链判断

  • 通过检查对象的__proto__prototype属性是否为Array的原型。
  • 示例:value.__proto__ === Array.prototypeObject.getPrototypeOf(value) === Array.prototype

instanceof操作符

  • 判断对象是否是某个构造函数的实例。
  • 示例:value instanceof Array

变量遮蔽

var a= '12'
(function a(){
	a='34'
	consol.log(a) //函数对象a
})();

会查找最近作用域中的变量,打印的地方最近的a是函数a,而函数的名称又是只读的,因此给函数a赋值字符串34就不生效

浮点数精度

  1. 将浮点数转换成整数进行计算,再将结果转回浮点数。如果位数过长容易导致结果溢出
  2. 四舍五入
  3. 转换成字符串处理
  4. 使用成熟的库

强缓存适合静态资源

  1. 更新频率低:css、js、图片等上线后一般不会频繁变动,属于“内容稳定、体积较大、可复用性高”的资源
  2. 可控的“缓存更新机制”:强缓存会发生的问题是:资源更新后,旧缓存可能还在用。但前端工程一般会配合 文件指纹 hash 来解决,既保证 命中率高,又保证 更新及时
  3. HTML一般变化频繁(比如新增功能、文案修改)所以适合协商缓存。如果html引用的静态资源发生改变那么对应的文件指纹hash也会改变,也就是html更改时对应静态资源更改会重新请求

进程、线程、协程的区别

进程

操作系统资源分配的基本单位。

  • 有自己独立的内存空间(代码段、数据段、堆、栈)。
  • 进程之间相互隔离,通信需要 IPC(管道、消息队列、共享内存)。
  • 创建/销毁开销大。

线程

操作系统调度的基本单位。

  • 属于进程的一部分,运行在进程里。
  • 同一进程的多个线程共享内存(堆/全局变量),但有各自的栈。
  • 切换开销比进程小。
  • 可能有线程安全问题(需要锁)。

协程

用户态的“轻量级线程”,由程序自身控制调度。

  • 不是操作系统调度,而是程序主动让出/切换。
  • 更轻量,不需要内核态切换。
  • 常用于异步编程、IO 密集场景。
  • 需要程序员写逻辑来决定何时切换。

出现网络波动,如何确保数据传输,保证用户体验

前端

  1. 乐观更新:如用户点赞时先把点赞数加上,如果请求成功则不做处理,如果请求失败则回滚(将点赞数减去)并提示
  2. 重连
  3. 离线队列:将用户操作保存在队列中,当重连成功后再发送,如:聊天应用的重发消息
  4. 资源嗅探:将未处理完成的数据存储在indexedDB中,下次再进来再重传时先检查本地数据库中是否有数据,如果有可以直接拿出来使用
  5. ui占位:loading、骨架屏、占位图等

网络层

  1. 心跳:每隔一段时间发送一个心跳包检测连接是否断开,如果断开则重连
  2. 断点续传

后端

  1. 幂等接口:保证接口的设计不管操作多少次得到的结果都是一样的(post请求可能难以保证,可以加唯一标识配合校验机制)
  2. 数据库事务

defineProperty不支持新增属性的响应式,vue2如何处理的

  1. 使用Vue.set / this.$set内部会使用 defineReactive 为新属性设置 getter/setter。
  2. 对于数组的函数做劫持

nextTick

Vue 的响应式系统会异步更新DOM。当修改响应式数据时,Vue 并不会立即更新DOM,而是先在内存中记录变化,等本轮事件循环(microtask 或 next tick)结束后再统一渲染。 DOM 更新完成之后执行回调

好处:

  1. 避免重复渲染,提高性能。
  2. 批量处理多次数据更新,只渲染一次 DOM。

修改数据后立即想获取最新 DOM、执行动画或操作元素。

any、unknown、never

any

任意类型,关闭类型检查

  • 可以赋值给任何类型
  • 任何类型都可以赋值给 any
  • 可以调用任意属性或方法,编译器不会报错

缺点:丧失类型安全,容易隐藏错误

unknown

未知类型,更安全的 any

  • 可以赋值给 anyunknown 类型
  • 不能直接赋值给其他类型,必须先做类型缩窄(类型检查或类型断言)

优点:保留灵活性,同时保证类型安全

never永远不会出现的类型

  • 用于永远不会有值的情况
  • 常用于函数抛出异常或死循环

vue-router的生命周期

类型钩子特点
组件内beforeRouteEnter进入前,无法访问 this
组件内beforeRouteUpdate当前组件复用时,路由改变
组件内beforeRouteLeave离开组件前,可访问 this
全局beforeEach全局前置守卫,可阻止导航
全局beforeResolve所有守卫及异步组件解析后执行
全局afterEach全局后置守卫,无法阻止导航

使用js实现不可变对象

  1. Object.freeze:将对象冻结,如果对象内部的属性有嵌套对象还需要递归冻结
  2. proxy拦截
握手阶段非对称加密安全交换对称密钥
数据传输阶段对称加密高效加密 HTTP 数据

非对称加密 → 保证密钥安全 对称加密 → 保证传输性能

get和post的区别

对比点GETPOST
语义获取数据提交数据
参数位置URL 查询参数请求体
长度限制理论上无限制
缓存可能缓存默认不缓存
幂等性
安全性参数暴露在 URL参数在 body,相对更安全
常见场景查询、搜索提交表单、上传文件

css实现三角形

原理:border 有宽度后,四角交接处会产生斜线,

块级作用域

es6之前没有块级作用域,所以在 forif 中用 var 声明的变量,会“泄漏”到外层作用域。

使用立即执行函数模拟块级作用域

(funciton(){
	var a=1
	console.log(a)
})()

异步组件

解决按需加载的问题

底层实现其实就是利用 JavaScript 的动态 import() + Promise + Vue 的响应式渲染机制来实现的。

cdn

类型说明注意点
静态资源JS、CSS、字体文件、图片、视频等内容不经常改动,缓存友好
第三方库Vue、React、Lodash、jQuery 等可以用官方 CDN,节省带宽
大文件/公用资源公用图片、图标、logo 等尽量去中心化,提升全球访问速度
不敏感内容不包含用户隐私或安全信息CDN 一般是公共访问

路由的实现原理

  1. 监听 URL 变化
  2. 根据路由匹配组件
  3. 更新视图(Vue 渲染管线)

vue事件和原生事件的区别

对比点Vue 事件原生事件
绑定方式@ (v-on 指令)addEventListener / onclick
this 指向组件实例(Vue 实例),可访问 datamethods触发事件的 DOM 节点
修饰符提供 .stop.prevent.once 等语法糖需手动调用 event.preventDefault()stopPropagation()
自定义事件子组件可用 $emit 触发,父组件用 @xxx 监听仅限于原生 DOM 事件
事件透传Vue2 用 .native;Vue3 用 $attrs不存在这个概念
解绑方式Vue 会在组件销毁时自动解绑需手动调用 removeEventListener

统一代码规范

语言规范

  • JavaScript/TypeScript:ESLint + Airbnb/Standard/自定义规则
  • CSS/SCSS/LESS:Stylelint + 规范化命名(BEM/SMACSS/ITCSS)
  • HTML/模板:格式化缩进、属性顺序、语义化标签 项目结构规范
  • 约定目录结构、文件命名、组件命名规则
  • 模块化方式:如 React/Vue/Angular 组件规范
  • 统一资源管理:图片、icon、字体、样式等 编码习惯
  • 缩进、分号、引号、行长度、空行规则
  • 函数、变量、组件命名规则
  • 注释规范:函数/类/接口文档化注释 Git 提交规范
  • 使用 commit message 规范,如 Conventional Commits
  • 分支策略:如 Git Flow、GitHub Flow
  • PR 模板,要求描述功能、截图、测试方法

为什么读取元素的宽高信息也会导致重排

有些属性会强制浏览器先把最新的样式和布局计算出来再返回值。

事件中的target和currentTarget

  • target:事件触发的实际元素
  • currentTarget:事件监听器绑定的元素,当前正在处理事件的元素

js执行阻塞页面渲染

原因:浏览器无法预测js是否会修改dom或样式,所以必须在执行完js前暂停渲染保证渲染的一致性

Map和WeakMap

特性MapWeakMap
键类型任意类型对象(弱引用)
遍历能力可遍历不可遍历
size 属性
垃圾回收键值存在Map的引用不回收键对象无引用可回收,如果没有外部使用它就会自动被垃圾回收
常用场景通用键值存储私有数据/对象缓存/防内存泄漏

微任务递归生成微任务会陷入死循环吗

不会陷入真正的“死循环”,但它可能会导致浏览器或 Node.js 的事件循环被连续占用,造成可感知的“卡顿”或性能问题。

微任务是异步执行的不会像普通函数递归那样占用调用栈 但事件循环会被微任务队列持续占用,页面或应用无法响应其他任务队列中的任务

避免方案

  1. 限制递归深度
  2. 使用宏任务分隔

为什么transform不会导致重排

transform 属于复合图层(composite layer)属性,只会修改元素在渲染层的矩阵变换(位置、旋转、缩放、倾斜等),而不改变 DOM 布局信息。

网站第一次访问和第二次访问的区别

第一次访问第二次访问
资源都要从服务器获取如果第一次访问的资源有缓存可以复用缓存
dns需要dns解析可能直接明州dns缓存
tcp和tls需要建立连接如果连接还在keep-alive时间内还能复用

CommonJS和ESModule

对比维度CommonJS (CJS)ESModule (ESM)
导入导出单一导入、单一导出按需导入、按需导出、默认导出
加载时机运行时加载,执行到 require 时才加载模块编译时静态分析,提前确定依赖关系
导出机制导出的是值拷贝(除对象/引用外)导出的是 实时绑定 (live binding),值会随变量变化
同步/异步同步加载,适合本地文件系统异步加载,适合浏览器环境
执行顺序按代码顺序加载import 提升(hoisting),在执行前先加载并解析
动态加载支持 require(path) 动态传参通过 import(path)(异步,返回 Promise)
环境支持Node.js 原生支持;浏览器不支持现代浏览器 & Node.js (v12+) 支持
Tree-shaking不支持(难以静态分析依赖)原生支持(构建工具可按需打包)

vite和webpack的区别

对比维度ViteWebpack
构建理念基于原生 ES Module,开发时不打包,直接利用浏览器加载基于打包,开发和生产都通过构建一个大 bundle
开发体验启动非常快:直接用esbuild预构建依赖,源码走 ESM启动慢:要先打完整个 bundle
热更新 (HMR)精准到模块级别,依赖变化只重新编译受影响的模块通过 dev-server,依赖变化会重新打包再替换
生产构建用Rollup打包,天然支持 Tree-shaking / Code Splitting用自己的 bundler pipeline(webpack+loader+plugin)
性能瓶颈大型项目在生产构建时 Rollup 可能较慢开发时打包大项目启动会很慢,但生产优化成熟
生态轻量、现代化,生态在快速发展,偏前端应用插件生态极其丰富,功能最全,历史项目普遍依赖
适用场景快速开发、现代前端(Vue3、React、TS)大型复杂项目、兼容性要求高、生态依赖重

hmr实现原理

vite 利用浏览器的ESM特性,修改文件后:

  1. 开发服务器(基于 esbuild + koa)检测到文件变化。
  2. 仅对变化的模块进行局部重新编译,通过WebSocket推送到浏览器。
  3. 浏览器使用 import() 重新加载模块,替换页面上的组件/逻辑。

更新快,因为不需要重新打包整个项目。

webpack 借助webpack-dev-server (WDS)或 webpack-dev-middleware

  1. 改动文件 → Webpack 增量重新打包,生成新的 bundle。
  2. 通过 WebSocket 通知浏览器。
  3. 浏览器 runtime (webpackHotUpdate) 替换旧模块,更新应用。

需要重新打包,随着项目变大,HMR 速度会下降。

生产环境的搭建

Vite 使用Rollup作为打包工具

  • Tree-shaking:基于 ESM 静态分析,删除未使用代码。
  • Code Splitting:支持动态 import,自动分包。
  • 静态资源处理:CSS、图片、字体等内置优化。

优点:产物非常干净,优化好,适合现代浏览器。 缺点:超大型项目构建速度比 Webpack 慢。

Webpack

  • Tree-shaking(基于 ESM)
  • SplitChunksPlugin(代码分割)
  • TerserPlugin(压缩)
  • MiniCssExtractPlugin(CSS 抽离)

优点:大量插件可供选择,生态极度丰富。 缺点:配置复杂,构建速度相对慢。

定时器精确度

为什么定时器会不准

  1. 事件循环机制:JS 是单线程,定时器回调需要等主线程空闲才会执行,可能被阻塞。
  2. 系统最小粒度:大多数浏览器有最小定时器间隔(通常是 1ms~4ms,后台标签页会被强制降频到 1000ms+)。
  3. 渲染节奏:浏览器大约每 16.67ms(60Hz 显示器)渲染一次,如果用 setInterval 做动画,容易和渲染不同步。
  4. 性能调度:CPU 省电模式、Tab 不可见、设备性能不足都会导致 drift(漂移)。
  5. 系统计时器也不精准:浏览器计时器也是调用的系统api,系统也是有误差的,只有原子钟才能做到严格精准

优化方案

  1. 使用时间戳:不要完全依赖 setInterval 的时间间隔,而是每次用实际时间差做修正
  2. 动画类:使用requestAnimationFrame
  3. 音频类:使用AudioContext.currentTime精度在亚毫秒级,常用于节拍器、音乐类应用
  4. Worker定时器:在webworker中运行计时器,减少主线程阻塞干扰

对象、数组相加为什么会自动转换toString

console.log([1,2]+[3,4]) //1,23,4 不是数组拼接,是转换成字符串拼接

+ 运算符在 JS 中有两种行为:

  1. 数字加法(如果两边都是数字)
  2. 字符串拼接(如果任意一边是字符串或对象被转换成字符串)

滚动

判断滚动触底

获取滚动容器的高度和当前滚动的高度做对比

判断向上滚还是向下滚

监听滚动事件用变量记录上次的滚动距离,将当前滚动距离与上次的对比

滚动加载dom过多的问题

  1. 虚拟滚动
  2. 节流滚动
  3. 使用intersectionObserver代替滚动事件
  4. 懒加载资源
  5. 分页加载(限制dom数量)

forEach

提前退出

无法使用break退出

如何实现提前退出

  1. 包装成函数return
  2. 抛出异常中断执行

能否异步

在 forEach循环中使用await,循环本身不会等待异步操作完成

  • Array.prototype.forEach 的回调函数不支持异步等待
  • 即使回调是 asyncforEach 仍然会立即迭代下一个元素,不会等待 await 完成

absolute

absolute会相对于 最近的有定位属性的祖先元素 来定位。 “有定位属性”:positionrelativeabsolutefixedsticky。 如果找不到这样的祖先,就会相对于 <html>(初始包含块,通常是 viewport) 来定位。

fixed

fixed 是相对于 视口(viewport) 来定位的。

js长任务阻塞页面渲染优化

  1. 切片执行
  2. requestIdleCallback空闲执行
  3. webwork后台执行
  4. 减少重排和重绘
  5. 代码分割,异步加载
  6. 先渲染可视区域

template里面响应式不用value

Vue 3 会在模板编译阶段自动识别 ref,直接把 .value 的值展开使用 JS 无法自动解包 .value,所以必须手动访问。

弱网检测

  1. NetworkInfomation获取网络信息,旧版本浏览器不支持
  2. online和offline事件,会在在线和离线时触发
  3. 请求一个已知大小的文件资源计算发送请求到加载完成的时间和文件大小,需要发起额外的请求
  4. navigator:online判断是否在线,connection获取网络连接属性

跨标签页通信

  1. BroadCast Channel:同源下浏览器不同窗口,Tab 页,frame 或者 iframe 下的(通常是同一个网站下不同的页面) 之间的简单通讯。创建一个监听某个频道下的 BroadcastChannel对象,接收发送给该频道的所有消息。全双工
  2. Service Worker
  3. Shared Worker
  4. 监听LocalStorage事件
  5. window.postMessage

伪元素和伪类的区别

  • 伪类 表示元素处于某种状态时应用的样式。
伪类含义示例
:hover鼠标悬停button:hover { color: red }
:focus元素获得焦点input:focus { border-color: blue }
:nth-child(2)第 2 个子元素li:nth-child(2) { font-weight: bold }
:disabled禁用状态button:disabled { opacity: 0.5 }
:not(.active)否定选择div:not(.active) { ... }
  • 伪元素 创造一个虚拟的“子元素”,用来修饰元素内容的一部分。 它不是 DOM 里的真实节点,但可以像元素一样加样式。
伪元素含义示例
::before元素内容前插入内容.btn::before { content: '★'; }
::after元素内容后插入内容.btn::after { content: '→'; }
::first-line选中第一行文字p::first-line { font-weight: bold }
::first-letter选中首字母p::first-letter { font-size: 2em }
::selection选中内容样式::selection { background: yellow }

内存泄漏的情况

  1. 全局变量:全局变量一直被window指向,因此不手动置为null就会一直存在
  2. console.log:使用控制台打印的对象它会一直在控制台显示,所以也会造成内存泄漏
  3. 没控制好的闭包
  4. dom泄漏:删除节点后被删除节点对它的子节点的引用还一直存在
  5. 使用容器中存储的数据:容器对其内部的数据有引用,如果数据不再使用建议移除,否则也会有内存泄漏。可以使用weakmap和weakset

贡献者

PinkDopeyBug

公告

本博客内容原本使用obsidian编写,由于没有仔细配置,以至图片引用使用obsidian风格。

且图片存储路径频繁变更导致部分文章图片无法正常显示。

为您带来不便请谅解。

ps:贡献者一直都只有wynnsimon一人,显示Pink的贡献者是因为我没好好配置git。后面因为懒就没一个个修改。如果被提及的人不希望被显示可以联系我我会立即删除。