12场景题
- BigInt js自带
- Decimal 库
浮点数精度
- 转换成整形
- 转换成字符串
- decimal库
大文件上传
- 切片上传:切片取名:hash+index,后端组合切片,切片时主线程卡顿:web-worker多线程切片。上传完成后通知后端合并切片
- 断点续传:选择文件后先请求后端是否有相同文件已上传一部分
- 断开重传:
- 资源嗅探:切片完后将blob存储到IndexedDB,下次用户进来后嗅探一下是否有未上传完的切片
截图
- canvans
- puppeteer
- html2canvas
移动端适配
- 不同端使用不同的网址
- 根据不同端加载不同的css
- 使用响应式(媒体查询)
水印功能
- 明水印(肉眼可见) 使用Canvas或css设置中背景图片水印。 svg动态水印
- 暗水印(肉眼不可见) 将水印内容写入到到二进制文件,读取时从文件中读取
静态资源失效
- 图片 占位图或out,图片失效时显示 重试、上报
- sss 重要的样式使用html内联的写法。
- cdn 本地备份,动态切换.
- 字体 降低字体(兜底).
图片加载性能优化
- 懒加载 在html中将图片的src属性更改为自定义的属性,这样浏览器遇到这个属性时就不会像默认的属性进行处理,当需要加载时将自定义属性的值赋值给src属性
获取到图片的dom,添加监听滚动事件,判断自身与视口的距离,达到一定距离就加载图片 这种方法的缺点是:不停的监听滚动事件,即使已经加载了图片,还是不断地触发事件,性能开销大 IntersectionObserver观察dom元素可视口的交叉区域,每次视口进入dom的显示区域都会调用回调函数。当图片加载后取消观察。
- 上拉加载和下拉刷新 监听滚动事件如果新的视口和原视口位置有一定的距离就请求数据并添加到数据列表中 监听触摸事件,下划的矩离与原距离有一定距离就请空教据列表并将请求的数据添加的到表中 同时也要实现节流,防抖
页面加载优化
代码优化
- 减少HTTP请求:合并文件、使用雪碧图、压缩资源。
- 优化代码结构:避免重复代码,使用函数和组件提高复用性。
- 懒加载:延迟加载非首屏资源,如图片、视频、组件。
资源优化
- 压缩资源:使用Gzip等压缩算法减小文件体积。
- 缓存资源:利用浏览器缓存,减少重复下载。
- 使用CDN:加速资源加载。
渲染优化
- 减少重排和重绘:避免频繁操作DOM,使用
DocumentFragment或虚拟DOM。 - 使用异步加载:如
async和defer加载脚本,不影响页面渲染。 - 服务端渲染(SSR):减少首屏加载时间,提升用户体验。
预加载和懒加载的区别
预加载
- 定义:在页面加载时或用户操作前,提前加载可能需要的资源。
- 优点:减少用户等待时间,提升体验。
- 缺点:增加初始加载时间,可能加载无用资源。
懒加载
- 定义:延迟加载非首屏或暂时不需要的资源,在需要时再加载。
- 优点:减少初始加载时间,优化性能。
- 缺点:可能增加用户等待时间,需要合理控制加载时机。
在window对象上绑定数据的问题
- 命名冲实 后绑定的重名数据系把前面的覆盖
- 安全风险 任何人可以获取和修改。
- 性能问题, 占用的存如果不解绑也不会清除
- 全局污染 任何人都可以获取到也可以修改
解决:模块化、命名空间、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.prototype或Object.getPrototypeOf(value) === Array.prototype
instanceof操作符
- 判断对象是否是某个构造函数的实例。
- 示例:
value instanceof Array
变量遮蔽
var a= '12'
(function a(){
a='34'
consol.log(a) //函数对象a
})();会查找最近作用域中的变量,打印的地方最近的a是函数a,而函数的名称又是只读的,因此给函数a赋值字符串34就不生效
浮点数精度
- 将浮点数转换成整数进行计算,再将结果转回浮点数。如果位数过长容易导致结果溢出
- 四舍五入
- 转换成字符串处理
- 使用成熟的库
强缓存适合静态资源
- 更新频率低:css、js、图片等上线后一般不会频繁变动,属于“内容稳定、体积较大、可复用性高”的资源
- 可控的“缓存更新机制”:强缓存会发生的问题是:资源更新后,旧缓存可能还在用。但前端工程一般会配合 文件指纹 hash 来解决,既保证 命中率高,又保证 更新及时。
- HTML一般变化频繁(比如新增功能、文案修改)所以适合协商缓存。如果html引用的静态资源发生改变那么对应的文件指纹hash也会改变,也就是html更改时对应静态资源更改会重新请求
进程、线程、协程的区别
进程
操作系统资源分配的基本单位。
- 有自己独立的内存空间(代码段、数据段、堆、栈)。
- 进程之间相互隔离,通信需要 IPC(管道、消息队列、共享内存)。
- 创建/销毁开销大。
线程
操作系统调度的基本单位。
- 属于进程的一部分,运行在进程里。
- 同一进程的多个线程共享内存(堆/全局变量),但有各自的栈。
- 切换开销比进程小。
- 可能有线程安全问题(需要锁)。
协程
用户态的“轻量级线程”,由程序自身控制调度。
- 不是操作系统调度,而是程序主动让出/切换。
- 更轻量,不需要内核态切换。
- 常用于异步编程、IO 密集场景。
- 需要程序员写逻辑来决定何时切换。
出现网络波动,如何确保数据传输,保证用户体验
前端
- 乐观更新:如用户点赞时先把点赞数加上,如果请求成功则不做处理,如果请求失败则回滚(将点赞数减去)并提示
- 重连
- 离线队列:将用户操作保存在队列中,当重连成功后再发送,如:聊天应用的重发消息
- 资源嗅探:将未处理完成的数据存储在indexedDB中,下次再进来再重传时先检查本地数据库中是否有数据,如果有可以直接拿出来使用
- ui占位:loading、骨架屏、占位图等
网络层
- 心跳:每隔一段时间发送一个心跳包检测连接是否断开,如果断开则重连
- 断点续传
后端
- 幂等接口:保证接口的设计不管操作多少次得到的结果都是一样的(post请求可能难以保证,可以加唯一标识配合校验机制)
- 数据库事务
defineProperty不支持新增属性的响应式,vue2如何处理的
- 使用
Vue.set/this.$set内部会使用defineReactive为新属性设置 getter/setter。 - 对于数组的函数做劫持
nextTick
Vue 的响应式系统会异步更新DOM。当修改响应式数据时,Vue 并不会立即更新DOM,而是先在内存中记录变化,等本轮事件循环(microtask 或 next tick)结束后再统一渲染。 DOM 更新完成之后执行回调
好处:
- 避免重复渲染,提高性能。
- 批量处理多次数据更新,只渲染一次 DOM。
修改数据后立即想获取最新 DOM、执行动画或操作元素。
any、unknown、never
any
任意类型,关闭类型检查
- 可以赋值给任何类型
- 任何类型都可以赋值给
any - 可以调用任意属性或方法,编译器不会报错
缺点:丧失类型安全,容易隐藏错误
unknown
未知类型,更安全的 any
- 可以赋值给
any或unknown类型 - 不能直接赋值给其他类型,必须先做类型缩窄(类型检查或类型断言)
优点:保留灵活性,同时保证类型安全
never永远不会出现的类型
- 用于永远不会有值的情况
- 常用于函数抛出异常或死循环
vue-router的生命周期
| 类型 | 钩子 | 特点 |
|---|---|---|
| 组件内 | beforeRouteEnter | 进入前,无法访问 this |
| 组件内 | beforeRouteUpdate | 当前组件复用时,路由改变 |
| 组件内 | beforeRouteLeave | 离开组件前,可访问 this |
| 全局 | beforeEach | 全局前置守卫,可阻止导航 |
| 全局 | beforeResolve | 所有守卫及异步组件解析后执行 |
| 全局 | afterEach | 全局后置守卫,无法阻止导航 |
使用js实现不可变对象
- Object.freeze:将对象冻结,如果对象内部的属性有嵌套对象还需要递归冻结
- proxy拦截
| 握手阶段 | 非对称加密 | 安全交换对称密钥 |
|---|---|---|
| 数据传输阶段 | 对称加密 | 高效加密 HTTP 数据 |
非对称加密 → 保证密钥安全 对称加密 → 保证传输性能
get和post的区别
| 对比点 | GET | POST |
|---|---|---|
| 语义 | 获取数据 | 提交数据 |
| 参数位置 | URL 查询参数 | 请求体 |
| 长度限制 | 有 | 理论上无限制 |
| 缓存 | 可能缓存 | 默认不缓存 |
| 幂等性 | 是 | 否 |
| 安全性 | 参数暴露在 URL | 参数在 body,相对更安全 |
| 常见场景 | 查询、搜索 | 提交表单、上传文件 |
css实现三角形
原理:border 有宽度后,四角交接处会产生斜线,
<!--等腰三角形-->
<div class="w-0 h-0 border-100 border-solid border-transparent border-b-blue">
</div>
<!--直角三角形-->
<div class="w-0 h-0 border-100 border-solid border-transparent border-b-blue border-l-red">
</div>
<!--等边三角形-->
<div class="w-0 h-0 border-100 border-solid border-transparent border-b-blue border-b-180">
</div>
<!--等腰梯形-->
<div class="w-50 h-50 border-50 border-solid border-transparent border-b-120 border-b-blue">
</div>块级作用域
es6之前没有块级作用域,所以在 for 或 if 中用 var 声明的变量,会“泄漏”到外层作用域。
使用立即执行函数模拟块级作用域
(funciton(){
var a=1
console.log(a)
})()异步组件
解决按需加载的问题
底层实现其实就是利用 JavaScript 的动态 import() + Promise + Vue 的响应式渲染机制来实现的。
cdn
| 类型 | 说明 | 注意点 |
|---|---|---|
| 静态资源 | JS、CSS、字体文件、图片、视频等 | 内容不经常改动,缓存友好 |
| 第三方库 | Vue、React、Lodash、jQuery 等 | 可以用官方 CDN,节省带宽 |
| 大文件/公用资源 | 公用图片、图标、logo 等 | 尽量去中心化,提升全球访问速度 |
| 不敏感内容 | 不包含用户隐私或安全信息 | CDN 一般是公共访问 |
路由的实现原理
- 监听 URL 变化
- 根据路由匹配组件
- 更新视图(Vue 渲染管线)
vue事件和原生事件的区别
| 对比点 | Vue 事件 | 原生事件 |
|---|---|---|
| 绑定方式 | @ (v-on 指令) | addEventListener / onclick |
| this 指向 | 组件实例(Vue 实例),可访问 data、methods | 触发事件的 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
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意类型 | 对象(弱引用) |
| 遍历能力 | 可遍历 | 不可遍历 |
| size 属性 | 有 | 无 |
| 垃圾回收 | 键值存在Map的引用不回收 | 键对象无引用可回收,如果没有外部使用它就会自动被垃圾回收 |
| 常用场景 | 通用键值存储 | 私有数据/对象缓存/防内存泄漏 |
微任务递归生成微任务会陷入死循环吗
不会陷入真正的“死循环”,但它可能会导致浏览器或 Node.js 的事件循环被连续占用,造成可感知的“卡顿”或性能问题。
微任务是异步执行的不会像普通函数递归那样占用调用栈 但事件循环会被微任务队列持续占用,页面或应用无法响应其他任务队列中的任务
避免方案
- 限制递归深度
- 使用宏任务分隔
为什么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的区别
| 对比维度 | Vite | Webpack |
|---|---|---|
| 构建理念 | 基于原生 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特性,修改文件后:
- 开发服务器(基于
esbuild+koa)检测到文件变化。 - 仅对变化的模块进行局部重新编译,通过WebSocket推送到浏览器。
- 浏览器使用
import()重新加载模块,替换页面上的组件/逻辑。
更新快,因为不需要重新打包整个项目。
webpack 借助webpack-dev-server (WDS)或 webpack-dev-middleware
- 改动文件 → Webpack 增量重新打包,生成新的 bundle。
- 通过 WebSocket 通知浏览器。
- 浏览器 runtime (
webpackHotUpdate) 替换旧模块,更新应用。
需要重新打包,随着项目变大,HMR 速度会下降。
生产环境的搭建
Vite 使用Rollup作为打包工具
- Tree-shaking:基于 ESM 静态分析,删除未使用代码。
- Code Splitting:支持动态 import,自动分包。
- 静态资源处理:CSS、图片、字体等内置优化。
优点:产物非常干净,优化好,适合现代浏览器。 缺点:超大型项目构建速度比 Webpack 慢。
Webpack
- Tree-shaking(基于 ESM)
- SplitChunksPlugin(代码分割)
- TerserPlugin(压缩)
- MiniCssExtractPlugin(CSS 抽离)
优点:大量插件可供选择,生态极度丰富。 缺点:配置复杂,构建速度相对慢。
定时器精确度
为什么定时器会不准
- 事件循环机制:JS 是单线程,定时器回调需要等主线程空闲才会执行,可能被阻塞。
- 系统最小粒度:大多数浏览器有最小定时器间隔(通常是 1ms~4ms,后台标签页会被强制降频到 1000ms+)。
- 渲染节奏:浏览器大约每 16.67ms(60Hz 显示器)渲染一次,如果用
setInterval做动画,容易和渲染不同步。 - 性能调度:CPU 省电模式、Tab 不可见、设备性能不足都会导致 drift(漂移)。
- 系统计时器也不精准:浏览器计时器也是调用的系统api,系统也是有误差的,只有原子钟才能做到严格精准
优化方案
- 使用时间戳:不要完全依赖
setInterval的时间间隔,而是每次用实际时间差做修正 - 动画类:使用requestAnimationFrame
- 音频类:使用AudioContext.currentTime精度在亚毫秒级,常用于节拍器、音乐类应用
- Worker定时器:在webworker中运行计时器,减少主线程阻塞干扰
对象、数组相加为什么会自动转换toString
console.log([1,2]+[3,4]) //1,23,4 不是数组拼接,是转换成字符串拼接+ 运算符在 JS 中有两种行为:
- 数字加法(如果两边都是数字)
- 字符串拼接(如果任意一边是字符串或对象被转换成字符串)
滚动
判断滚动触底
获取滚动容器的高度和当前滚动的高度做对比
判断向上滚还是向下滚
监听滚动事件用变量记录上次的滚动距离,将当前滚动距离与上次的对比
滚动加载dom过多的问题
- 虚拟滚动
- 节流滚动
- 使用intersectionObserver代替滚动事件
- 懒加载资源
- 分页加载(限制dom数量)
forEach
提前退出
无法使用break退出
如何实现提前退出
- 包装成函数return
- 抛出异常中断执行
能否异步
在 forEach循环中使用await,循环本身不会等待异步操作完成
Array.prototype.forEach的回调函数不支持异步等待- 即使回调是
async,forEach仍然会立即迭代下一个元素,不会等待await完成
absolute
absolute会相对于 最近的有定位属性的祖先元素 来定位。 “有定位属性”:position 为 relative、absolute、fixed、sticky。 如果找不到这样的祖先,就会相对于 <html>(初始包含块,通常是 viewport) 来定位。
fixed
fixed 是相对于 视口(viewport) 来定位的。
js长任务阻塞页面渲染优化
- 切片执行
- requestIdleCallback空闲执行
- webwork后台执行
- 减少重排和重绘
- 代码分割,异步加载
- 先渲染可视区域
template里面响应式不用value
Vue 3 会在模板编译阶段自动识别 ref,直接把 .value 的值展开使用 JS 无法自动解包 .value,所以必须手动访问。
弱网检测
- NetworkInfomation获取网络信息,旧版本浏览器不支持
- online和offline事件,会在在线和离线时触发
- 请求一个已知大小的文件资源计算发送请求到加载完成的时间和文件大小,需要发起额外的请求
- navigator:online判断是否在线,connection获取网络连接属性
跨标签页通信
- BroadCast Channel:同源下浏览器不同窗口,Tab 页,frame 或者 iframe 下的(通常是同一个网站下不同的页面) 之间的简单通讯。创建一个监听某个频道下的
BroadcastChannel对象,接收发送给该频道的所有消息。全双工 - Service Worker
- Shared Worker
- 监听LocalStorage事件
- 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 } |
内存泄漏的情况
- 全局变量:全局变量一直被window指向,因此不手动置为null就会一直存在
- console.log:使用控制台打印的对象它会一直在控制台显示,所以也会造成内存泄漏
- 没控制好的闭包
- dom泄漏:删除节点后被删除节点对它的子节点的引用还一直存在
- 使用容器中存储的数据:容器对其内部的数据有引用,如果数据不再使用建议移除,否则也会有内存泄漏。可以使用weakmap和weakset
贡献者
版权所有
版权归属:wynnsimon
