1. 什么是AST?它在前端有哪些应用场景?
AST Abstract Syntax Tree抽象语法树,用于表达源码的树形结构
应用:
- Babel:一个广泛使用的 JS 编译器,将ES6+ 或 JSX 等现代语法转换为兼容性较好的 ES5 代码。
- Eslint:JS 静态代码分析工具,它基于 AST 来分析代码中潜在问题和违反的代码规范。
- Terser:JS 压缩工具,使用 AST 进行代码压缩和混淆
- webpack 和 Rollup:构建工具使用AST 来优化 JS 和其他前端资源的打包过程。
AST 是前端开发中处理代码、优化构建、提高代码质量和实现跨语言支持的核心工具之一。它帮助前端工具深入理解代码结构,从而对代码进行高效的转化、优化和分析。通过AST,前端开发者能够更加轻松地进行代码转换、静态检查、压缩和混淆等操作,从而提高效率和应用性能。
2.中序遍历、前序遍历、后续遍历
前序遍历:根左右 ABDECFG
中序遍历: 左根右 DBEAFCG
后序遍历:左右根 DEBFGCA
3.什么是 BFF,它又有什么用?
BFF (Backend For Frontend)是一种架构模式,专门为前端应用提供定制化的后端服务。它的核心思想是为不同的前端客户端(如web、移动端、桌面端等)提供专门的后端服务,而不是让所有的客户端共享一个通用后端API
用途:
1)优化前端性能:
- 减少请求次数:BFF 可以将多个后端API的请求合并为一个,减少前端请求的次数。
- 减少数据传输量:BFF 可以根据前端的需求,对后端返回的数据进行聚合、过滤或转换、减少不必要的数据传输。
2)解耦前端和后端
- 独立开发
- 灵活适配:BFF可以根据不同的客户端(web、移动端)提供不同的API,而不需要修改后端服务
3)统一安全控制
在 BFF 层集中处理身份验证、权限管理、请求限流等安全策略,避免分散到各个微服务。
4)缓存与容错:缓存高频访问数据(如用户信息),减少后端压力。实现重试、熔断等机制。增强系统容错性
4.同一个url地址,如何实现手机打开是移动端页面,电脑打开是web页面
- 流体布局: 100% 、相对单位rem、 flex、grid
- 媒介查询(media query):一套代码
css 判断(@media),js逻辑(matchMedia)
存在问题: 1.差异化越大,开发维护成本越大;2.打包体积(PC + Mobile)
结论:适合差异化较小的站点 - 服务器判断请求头:user-agent :独立开发、分开开发、独立部署
5.当 QPS 达到峰值是,该如何处理?
- 缓存:本地缓存、服务器缓存、CDN 预热…
- 减少请求:合并请求、雪碧图…
- 延时请求: 懒加载…
- 扩容
- 数据库优化:sql优化、索引、读写分离
- 负载均衡
- 监控报警
6 如何实现精确的setInterval?
1.根据 performance.now 动态补偿
2.优先使用 requestAnimationFrame 替代定时器,与屏幕刷新率同步(约 16.67ms/帧)【不受失活页面影响,受很多其他因素影响】
3.webworker 【不受失活页面影响,不受渲染帧的影响】
7.页面上有100万个任务需要执行,如何保证页面不卡顿
- 任务分批处理: requestAnimationFrame 或者 setTimeout 实现异步调度
- requestIdleCallback:在浏览器空闲时执行低优先级任务;
- Web Workers:将计算密集型任务移到其他线程:【缺陷:1.无法直接操作 DOM;2.线程间通信(postMessage)有性能开销,需减少数据传递频率】
8.死循环会导致什么后果?无限递归会导致什么后果?
死循环:页面卡顿、无响应
无线递归: 堆栈溢出、程序崩溃
9.后端响应巨量数据,前端如何避免其性能问题?
减少主线程负载,分而治之。优先通过流式传输、分页、Worker多线程减轻数据压力,再结合虚拟化、Canvas等渲染优化提升用户体验。
- 网络性能:
1. 改接口规格
2. sse( server sent event ) / websocket 实时推送
3. 流式读取: etch stream
const resp = await fetch("XXXX");//等待响应头到达
const body = await resp.json();//等待响应体
- 渲染性能:
- 分页(产品参与协调)
- 分片渲染 (React Fiber)
- 虚拟滚动
- canvas
10.vue 项目有哪些常见的优化手段?
-
图片懒加载、路由懒加载
-
suspence 【用于管理异步操作(如数据加载、代码分割)的组件】
-
v-if 和 v-show
-
打包手段优化
-
网络手段优化
- CDN (公共库)
- http2
- 图片格式优化
- gzip
- 请求合并
- GraphQL
-
key
-
computed
-
保持对象地址的稳定性
-
延迟装载
-
v-model lazy 延时双向绑定
-
冻结对象(vue2)
-
函数式组件(vue2)
11. isNaN 和 Number.NaN 的函数区别?
isNaN 和 Number.isNaN 是 JavaScript 中用于判断 NaN (Not-a-number)的函数。
isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的值都会返回 true,isNaN(‘true’) === true;
Number.isNaN 会首先判断传入的参数是否是数字,如果是数字再继续判断是否 NaN。
12. Map和object的区别?
Map | Object | |
---|---|---|
构造方式 | new Map()创建 | 通过字面量、new Object()、Object.create()等方式创建 |
键的类型 | Map的键可以是任意类型,包括对象、数组、函数等 | 只能是字符串或者Symbol,其他类型的键会被转换为字符串 |
键的顺序 | 有序 | 无序 |
键值大小 | 使用 .size 属性来获取其大小。使用 .get() 方法获取值。 | 没有内置的方法来获取其大小(键值对的数量)采用Object.keys(xxx).length获取大小。使用 . 获取值。 |
原型继承 | 不会从原型链中继承属性 | 会从原型链中继承属性 |
迭代 | 可以直接迭代 | 获取到键值后才能迭代 |
性能 | 在频繁添加和删除键值对性能更好 | 在频繁添加和删除键值对未作出优化 |
13. map和weakMap的区别?
Map | weakMap | |
---|---|---|
键的类型 | 可以使用任何类型的值作为键,包括原始类型(如字符串、数字)和对象。 | 只能使用对象作为键 |
垃圾回收机制 | 键是 强引用。即使键对象已无其他引用,只要 Map 存在,该对象仍不会被垃圾回收,可能导致内存泄漏。 | 键是 弱引用。当键对象无其他引用时,垃圾回收机制会自动回收该键及其关联的值,避免内存泄漏。 |
迭代和操作 | 支持 遍历操作(如 forEach、for…of),可通过 keys()、values()、entries() 获取迭代器。内置 size 属性,可获取键值对数量。 | 不支持遍历,无 size 属性,也无法直接获取所有键值对。设计初衷是保护键对象的隐私和自动清理,因此不暴露遍历接口。 |
14. JavaScript 标准内置对象
- 基本对象
构成 JavaScript 的核心基础,用于定义其他对象或行为:- Object:所有对象的基类,提供属性操作(如 hasOwnProperty())
- Function:函数的构造器,每个函数都是其实例
- Boolean 和 Symbol:分别表示布尔值和唯一符号的包装器
- 处理特定类型的数据:
- String:字符串操作(如 charAt()、replace())
- Number 和 BigInt:数值类型,后者支持大整数运算
- Boolean:布尔值的包装类型(注意与基本类型的 true/false 区分)
- 集合与数据结构
存储和管理数据集合:- Array:有序元素集合,支持动态操作(如 push()、map())
- Map 和 Set:键值对集合与唯一值集合
- WeakMap:弱引用键值对,避免内存泄漏
- 错误处理对象
捕获和管理运行时错误:- Error:基础错误类型
- 子类:TypeError(类型错误)、SyntaxError(语法错误)、ReferenceError(引用错误)等 。
- 数学与日期对象
处理数学运算和日期时间:- Math:提供数学函数(如 Math.random())和常量(如 Math.PI)
- Date:解析和计算日期时间(如 getFullYear())
- 其他功能对象
扩展语言能力的关键对象:- JSON:数据序列化与解析(JSON.stringify()、JSON.parse())
- RegExp:正则表达式,用于字符串模式匹配
- Promise:管理异步操作
- Proxy:对象代理,支持元编程(如拦截对象操作)
- 全局属性与函数
全局作用域下的属性和工具函数:- 值属性:Infinity(无穷大)、NaN(非数字)、undefined
- 全局函数:eval()(执行字符串代码)、parseInt()(字符串转整数)、isNaN()(判断非数字)
15.数组有哪些原生方法
方法 | 作用 | 是否影响原数组 |
---|---|---|
push | 在数组后添加元素,返回长度 | 是 |
pop | 删除数组最后一项,返回被删项 | 是 |
shift | 删除数组第一项,返回删除项 | 是 |
unshift | 数组开头添加元素,返回长度 | 是 |
reserve | 反转数组,返回数组 | 是 |
sort | 排序数组,返回数组 | 是 |
splice | 截取数组,返回被截取部分 | 是 |
join | 将数组变成字符串,返回字符串 | 否 |
concat | 连接数组 | 否 |
map | 相同规则处理数组项,返回新数组 | 否 |
forEach | 遍历数组 | 否 |
filter | 过滤数组项,返回符合条件的数组 | 否 |
every | 每一项符合规则才返回 true | 否 |
some | 只要有一项符合规则就返回 true | 否 |
reduce | 接收上一个 return 和数组下一项 | 否 |
flat | 数组扁平化 | 否 |
slice | 截取数组,返回被截取区间 | 否 |
16.Unicode、UTF-8、UTF-16、UTF-32的区别?
17.escape、encodeURI、encodeURIComponent的区别
方法 | 编码对象 | 编码范围 | 编码规则 | 兼容性 |
---|---|---|---|---|
escape | 字符串 | 除 ASCII字母、数字、@*/+ 外的字符均编码为 %XX 或 %uXXXX(Unicode编码) | 直接对字符的Unicode编码加 %u 前缀,仅支持ASCII字符(≤255),已过时且不推荐使用 | 不适用于URL,仅旧环境兼容 |
encodeURI | 完整URL | 保留URL合法字符(如 😕?#[]@ 等),仅编码空格、中文等非法字符为 %XX | 将非法字符转换为UTF-8字节后编码,保持URL结构完整 | 适用于编码整个URL |
encodeURIComponent | URL组成部分(如参数) | 编码范围更广,包括 😕?# 等保留字符,仅保留 !*()’ 和基本ASCII字符 | 对字符进行更严格编码,确保参数值安全传输,常用于拼接查询字符串 | 适用于URL参数或路径片段 |
18. JavaScript为什么要进行变量提升,它导致了什么问题?
变量提升是JavaScript早期设计中的权衡产物,虽然提升了性能和容错性,但也带来了作用域混乱、变量污染等问题。现代开发中,应优先使用let/const,并通过规范代码结构规避潜在风险
19. ESM 和 CommonJS 有什么区别?
ESM | CMJ | |
---|---|---|
标准来源 | 官方标准(新增语法) | 社区标准(新增API) |
导入机制 | 静态声明依赖,模块关系在编译时确定,支持 Tree Shaking 优化 | 动态加载,模块运行时动态生成,灵活性高 |
加载机制 | 编译时静态分析依赖,支持异步加载 | 运行时同步加载,阻塞后续代码执行 |
动态加载 | 支持 import 动态导入(返回 Promise) | 直接使用 require()动态加载 |
作用域 | 块级作用域,严格模式(this 为 undefined) | 共享全局作用域,可能污染变量 |
20.use strict 是什么意思 ? 使用它区别是什么?
是 JavaScript 中用于启用 严格模式(Strict Mode) 的指令,由 ECMAScript 5(ES5)引入。其核心目的是通过更严格的语法和运行时检查,减少代码中的潜在错误,提升安全性及性能。
区别:
- 构造函数必须使用 new;
- 变量必须声明
- 禁止使用eval、 with 语句;
- 禁止 this 关键字指向全局对象;
- 对象不能有重名的属性;
优势: - 消除静默错误:将隐式错误转为显式报错,提升代码健壮性
- 提高安全性:限制危险操作(如 eval、with),减少漏洞风险
- 优化性能:静态分析支持编译器优化(如 Tree Shaking)
21.for…in和for…of的区别
for…in | for…of | |
---|---|---|
迭代对象 | 遍历对象的可枚举属性(包括继承的属性),适用于普通对象、数组(但不推荐) | 遍历可迭代对象(如数组、字符串、Map、Set等),要求对象实现 Symbol.interator接口 |
迭代内容 | 返回键名 | 返回值 |
遍历顺序 | 不确定(对象属性无固定顺序) | 确定(按可迭代对象的顺序,如数组索引顺序) |
兼容性 | – | ES6+ |
性能 | 会检查原型链,性能较低 | 对数组的遍历效率接近 传统 for 循环 |
22. Html 5 有哪些特性
- 语义化标签: header、footer、nav、section、article、aside
- 增强型表单: date、email、number、range、search、tel 等
- 视频和音频: audio、video
- Canvas 绘图、SVG 绘图
- 地理定位: Geolocation
- 拖放: drag
- web Workers: 是运行在后台的 Javascript ,独立于其他脚本,不会影响页面性能
- web storage: localStorage、sessionStorage
- webSocket: HTML5 开始提供一种在单个 TCP 连接上进行全双工通讯的协议
23. 如何实现浏览器内多个标签页之前的通信
场景 | 推荐方案 | 关键考量 |
---|---|---|
同源简单数据同步 | LocalStorage | 兼容性优先,数据量小 |
同源实时通信 | Broadcast Channel | 低延迟,API简洁 |
跨域通信 | window.postMessage | 跨域需求,安全验证 |
复杂计算与资源共享 | ShareWorker | 高性能,长期任务处理 |
高并发实时应用 | WebSocket | 服务端支持,低延迟。 [支持跨域] |
离线与后台消息 | Service Worker | 离线场景,消息中转 |
总结:
- 明确支持跨域:Window.postMessage、WebSocket
- 间接支持跨域:Service Worker(需其他技术辅助)
- 仅在同源:localStorage、Broadcast Channel 、ShareWorker
24. HTML5 离线缓存
基于一个manifest 文件( 缓存清单文件,后缀为 .appcache ) 的缓存机制(不是存储技术)
主要通过 html 元素的 manifest 属性指定一个后缀为 manifest 的文件,该文件为网页指定哪些文件需要被缓存,哪些不需要缓存,已经获取失败的处理方式等等。
25. FCP 和 LCP 区别?
FCP(First Contentful Paint)和LCP(Largest Contentful Paint)是衡量网页加载性能的两个关键指标,它们的核心区别在于 关注的内容阶段和用户体验的维度不同
维度 | FCP【First Contentful Paint】 | LCP【Largest Contentful Paint】 |
---|---|---|
测量元素 | 首个文本、图片、Canvas 等任意内容 | 视口内最大的图片、视频、背景图或者大段文本块 |
元素重要性 | 可能包含非关键内容(如加载动画) | 聚焦用户关注的“核心内容” |
动态变化 | 仅记录第一次内容渲染 | 页面加载过程中可能多次更新(最大元素变化时) |
用户交互影响 | 不受用户操作影响 | 用户首次滚动或者输入后停止记录 |
性能标准 | 无固定阈值,但建议控制在 1s 以内,以提升用户对加载速度的感知 | 理想值为 2.5 s内,超过则视为影响用户体验 |
26. 说一说 JS 垃圾回收机制?
-
什么是垃圾
- 定义:程序中不再被引用的变量或对象,例如函数执行后未被外部引用的局部变量
- 生命周期:
- 全局变量:持续到页面关闭
- 局部变量:函数执行后即可被回收,除非被闭包或外部引用保留
-
为什么需要垃圾回收
Javascript 动态分配内存的特性(如变量、对象、函数的创建)可能导致内存泄漏,长期积累会导致程序性能下降甚至崩溃
垃圾回收的核心算法
-
引用计数法
- 原理:记录每个对象的引用次数,当引用次数归零时回收内存
- 实例:
let objA = {}; // 引用计数为1 let objB = objA; // 引用计数为2 objA = null; // 引用计数为1
- 缺陷:无法处理循环引用(如两个对象互相引用),导致内存泄漏
-
标记清除法
- 原理:
- 标记阶段:从滚对象(全局变量、当前执行上下文)出发,标记所有可达对象
- 清除阶段:释放未被标记的对象内存
- 优点:解决循环引用问题,是现代浏览器的默认算法(如 Chrome V8 引擎)
- 缺点:产生内存碎片,可能影响大对象的内存分配效率
- 原理:
-
标记整理法:
- 优化点: 在标记清楚后,将存活对象移动到内存一端,消除碎片,提升内存连续性。
V8 引擎的优化策略
- 分代回收
- 新生代:存放短生命周期对象(如临时变量),使用 Scavenge 算法(复制存活对象到新空间,快速回收)
- 老生代:存放长生命周期对象(如全局变量),使用 标记-清除 + 标记-整理 组合算法
- 增量回收:将垃圾回收任务拆分为多个小步骤,穿插在代码执行中,减少主线程阻塞时间
- 并发回收:在后台线程执行垃圾回收,避免主线程卡顿
常见内存泄漏场景与预防
-
全局变量:
- 实例:未使用 var/let/const 声明的变量意外成为全局变量
- 解决:严格声明变量,及时置为 null
-
闭包:
- 示例:函数内变量被闭包引用,导致函数执行后无法回收。
- 解决:在闭包内不再使用时手动杰出引用
-
未清除的定时器与事件监听器
- 示例:setinterval 或 DOM 事件未及时清理
- 解决:使用 clearInterval 或者 removeEventlistener
-
DOM 引用
- 示例:保存已移除的 DOM 元素的引用,阻碍其内部释放
- 解决: 移除 DOM 后置相关变量为 null
内存优化建议
- 减少内存分配频率:重用对象或数组,避免频繁创建新对象
- 使用弱引用:通过 WeakMap 或 WeakSet 避免强引用导致的内存保留
- 监控工具:利用 Chrome DevTools 的 Memory 面板分析内存使用情况,识别泄漏点
Javascript 的垃圾回收机制通过自动管理内存,简化开发者工作,但其底层算法(如标记清除和分代回收) 仍需要开发者理解以避免内存泄漏。通过**合理编码习惯(如及时解除引用、使用弱引用)**和工具监控,可以有效优化内存使用。
27. 如何封装一个request?
一. 核心思路
- 代码复用性:避免每个请求重复编写基础配置(如超时、请求头、鉴权),统一管理请求逻辑
- 统一拦截与处理:通过拦截器集中处理错误、权限校验、数据格式转换等通用逻辑,提到代码可维护性
- 规范接口管理:模块化分类API,便于多人协作和后期扩展
二.进阶设计
- 取消重复请求:利用 Axios 的 CancelToken 或者 AbortController 终止重复/过时请求
- 请求重试机制:对网络错误或超市请求自动重试,提升用户体验
- 缓存与节流:对高频 Get请求添加缓存策略,减少服务器压力
- Typescript 支持:定义接口返回类型,增强代码健壮性
三. 总结
封装 Request 的核心在于标准化流程与灵活扩展结合。通过拦截器实现统一逻辑,模块化提升可维护性,同时结合团队规范(如错误码处理、日志上报)打造高可用请求层。【解决项目痛点(如鉴权失败、接口混乱等)】
28. 什么是 JWT ?
JWT (JSON Web Token)是一种基于开放标准(RFC 7519)的跨域认证和授权解决方案,其核心是通过 JSON 格式的令牌在各方之间安全传输信息。
定义:
JWT 是一种紧凑且自包含的令牌,由三部分组成:Header(头部)、Payload(荷载)和 Signature(签名),三者用点(.)连接,形如xxx.yyyy.zzzzz
- Header:包含令牌类型(JWT)和签名算法(如HS256、RSA),经 Base64Url编码后形成第一部分
- Payload:存储用户身份、权限等声明(Claims),分为注册声明(如 exp 过期时间)、公共声明和私有声明,同样经 Base64Url 编码
- Signature:通过 Header 中指定的算法对 Header 和 Payload进行加密生成,用于验证数据完整性和来源真实性
应用场景:
- 用户授权:JWT 最常见的用途是身份验证。用户登陆后,后续请求携带 JWT令牌,服务器无需存储 Session 信息,特别适用无状态 RESTfull API 和单点登录(SSO)场景
- 信息交换:JWT 可通过 前面确保数据未被篡改,适用于服务传递用户上下文西信息(如 微服务架构中的权限验证)
- 分布式系统:由于 JWT 的无状态特性,服务端无需维护会话信息,可轻松实现跨域认证和高可用扩展性
工作原理
1. 生成与验证流程
- 用户登录后,服务端生成 JWT 并返回客户端(通常存储在 LocalStorage 或 Cookie)
- 客户端在请求头 Authorization: Bearer < JWT> 中携带令牌
- 服务端验证签名有效性、过期时间等,通过后授权访问资源
2. 签名机制 :使用密钥(HMAC 算法)或公钥私对(如 RSA) 对Header 和 Payload 进行加密,防令牌篡改。
JWT 优缺点
优点 | 缺点 |
---|---|
无状态:服务端无需存储会员,降低服务器压力 | 不可撤销:令牌在有效期内无法强制失效,需依赖黑名单或短有效期 |
跨域友好:适用于前后端分离和微服务架构 | 数据透明:Payload 内容可被 Base64 解码,需避免存储敏感信息 |
自包含性:减少数据库查询,提升性能 | 密钥管理复杂:需安全存储签名密钥,防止泄漏 |
与传统 Session【token】认证对比
维度 | JWT | 传统 Token |
---|---|---|
结构组成 | 三部分:Header(算法)、Payload(数据)、Signature(签名) | 通常为简单字符串(如 UUID),无固定结构 |
信息存储 | 用户信息直接编码在 Payload 中,可包含角色、权限等元数据 | 仅存储标识符(如 sessionID),需服务器关联数据库查询用户信息 |
验证方式 | 通过密钥解密签名验证完整性,无需查库 | 需查库验证 Token 有效性及关联的用户信息 |
无状态性 | 服务端无需存储用户状态,适合分布式系统 | 依赖服务端存储(如 Redis),扩展性受限 |
- Session 认证:依赖服务器存储会话 ID,存在扩展性差、跨域限制和 CSRF 攻击风险
- JWT 认证:令牌存储在客户端,无状态且支持跨域,但需处理令牌失效和密钥管理问题
29. 什么是 RESTful API?
RESTful API (Representational State Transfer API)是一种基于 REST 架构风格设计的 Web 服务接口,旨在通过标准的 HTTP 协议和统一的接口规范,实现资源的访问与操作。其核心思想是将网络中的资源(如用户、订单、商品等)抽象为 URL ,客户端通过 HTTP 方法(Get、Post、Put、Delete 等)对资源进行增删改查(CRUD)操作,并以 JSON、XML 等格式返回结果。
典型应用场景
- 跨平台服务: 适用于 Web、移动端、Iot设备,通过标准化接口实现数据交互
- 微服务架构:服务间通过 RESTful API 传递资源信息,降低耦合
- 高频数据访问:结合缓存和负载均衡,提升系统吞吐量
RESTful API 通过 资源抽象和 HTTP 标准化,提供了一种简洁、高效且易用于扩展的接口设计范式,适用于现代分布式系统和高并发场景。其核心价值在于 降低系统耦合度 和提升开发协作效率
30. 闭包(Closure)有什么优缺点?
闭包是一个函数与其创建时所处词法环境的组合。
优点:
-
变量保护与封装性
- 作用域隔离:闭包允许函数访问外层作用域的变量,同时将这些变量封装在私有作用域中,避免全局变量污染。例如:通过闭包实现模块化代码,仅暴露必要接口
- 创建私有成员: 通过闭包可以模拟私有变量和方法,增强代码安全性。例如:计数器,count 变量被闭包保护,外部无法直接修改
-
持久化状态管理
- 跨调用保留状态: 闭包使函数内部的变量在外部函数执行后仍驻留内存,适合需要跟踪状态场景(如计数器、缓存)
-
灵活性与代码复用:
- 延迟执行与高阶函数:闭包可延迟代码执行(如事件回调),或用于函数工厂(柯里化)。例如 makeAdder 函数通过闭包生成不同加法函数
- 模块化开发:闭包将相关逻辑与数据封装为独立模块,提升代码可维护性。例如:isFirstLoad 函数管理传入值的唯一性
-
解决特定编程问题
- 避免循环引用问题:在异步操作(如 setTimeout)中,闭包可捕获循环变量的当前值,解决索引值错误问题。例如,循环事件示例中,闭包通过立即执行函数保存循环变量 i 的 瞬时值。
缺点
-
内存泄漏风险
- 变量常驻内存: 闭包导致外层函数变量无法被垃圾回收(GC),大量闭包可能引发内存泄漏,尤其在旧版 IE 浏览器中。例如,内部函数持续引用外部变量 counter,导致内存无法释放。
- 解决方案:
- 及时释放无用闭包(如将闭包变量设置为null)
- 使用弱引用(WeakMap)存储外部变量
-
性能损耗:
- 作用链查找开销:闭包访问外部变量需逐级查找作用域链,可能影响性能。例如,高频调用的闭包函数在嵌套较深是性能下降显著
- 内存占用增加:闭包需额外存储词法环境,内存消耗高于普通函数
-
调试与维护复杂度
- 作用域链复杂性:多层闭包嵌套时,变量的来源和值可能难以追踪,增加调试难度。例如,循环闭包问题中,变量 item 的意外覆盖需要块级作用域(let )解决
- 代码可读性降低:闭包的隐式依赖和状态管理可能使逻辑更隐晦,对新手不友好
-
意外副作用
- 变量值被外部修改:闭包可能在外层函数之外改变其内部变量,若设计不当会导致不可预期行为。例如,未合理控制闭包对外部变量的修改权限
闭包是一把双刃剑,其优势在于灵活的状态管理和封装能力,但需警惕内存与性能问题。合理使用时,闭包能显著提升代码质量;滥用则可能导致维护灾难。
31. 控制对象属性为只读可通过多种方式实现?
一. Object.defineProperty :精确控制属性特性
通过属性描述符 writable 或 get 方法实现只读
const obj = {};
// 方式1:设置 writable 为 false
Object.defineProperty(obj, "readOnlyProp", {
value: 42,
writable: false, // 不可修改
configurable: false // 不可删除或重定义
});
// 方式2:仅定义 getter(无 setter)
Object.defineProperty(obj, "readOnlyProp", {
get() { return 42; } // 不定义 set,赋值操作静默失败
});
特点:
- 精准控制单个属性的读写行为
- 非严格模式下赋值静默失败,严格模式下报错
- configurable:false 可防止后续修改属性特性
二. Object.freeze:冻结整个对象
使所有属性对象不可修改、删除或新增
const obj = { prop: 100 };
Object.freeze(obj);
obj.prop = 200; // 修改无效(严格模式报错)
注意
- 浅冻结,嵌套对象需递归冻结
- 适用于需要一次性锁定多个属性的场景
三.Proxy 代理:拦截属性操作
通过代理的 set 陷阱拦截赋值行为
const obj = { readOnlyProp: 42 };
const proxy = new Proxy(obj, {
set(target, prop) {
if (prop === "readOnlyProp") {
throw Error("此属性只读");
}
return Reflect.set(...arguments);
}
});
proxy.readOnlyProp = 100; // 抛出错误
优势:
- 灵活拦截多种操作
- 支持动态条件控制(如某些属性可读性,某些只读)
四.访问器属性(getter/setter)
通过定义 get 方法但不定义 set ,或抛出错误
// 方式1:仅定义 getter
const obj = {
get readOnlyProp() { return 42; }
};
obj.readOnlyProp = 100; // 静默失败
// 方式2:定义 setter 并限制
const obj = {};
Object.defineProperty(obj, "readOnlyProp", {
get() { return 42; },
set() { throw Error("禁止修改"); }
});
适用场景:
- 动态计算属性值
- 结合私有变量实现数据保护
五.类私有属性(#语法)
通过 # 前缀定义仅在类内部可访问的属性
class MyClass {
#secret = 42;
getSecret() { return this.#secret; }
}
const obj = new MyClass();
obj.secret; // undefined(外部无法访问)
特点:
- 严格实现私有性,而非直接只读
- 需通过类方法间接暴露只读访问
六.其他场景补充
- HTML 元素属性(input.readOnly )
// 正确设置只读(布尔属性只需存在,值无关)
document.getElementById("input").readOnly = true;
- 闭包封装
const createReadOnly = () => {
let value = 42;
return { getValue: () => value };
};
const obj = createReadOnly();
obj.getValue(); // 42,无法直接修改
总结与选择建议
场景 | 推荐方法 |
---|---|
单个属性精确控制 | Object.definePropery |
全对象冻结 | Object.freeze |
动态拦截或复杂逻辑 | Proxy |
动态计算或模块化封装 | getter/setter |
DOM 元素属性控制 | element.readOnly |
类内部私有属性保护 | # 语法 |
注意事项:
- 严格模式:非严格模式下部分操作静默失败,建议启用严格模式以捕获错误
- 性能:Proxy 和 递归 freeze 可能影响性能,需权衡使用
- 兼容性:Proxy 和 # 语法需 ES6+ 环境支持
32.工程内如何修改npm 包里面的变量?
一.临时修改:直接编辑 node_modules (不推荐但快速)
二.补丁方案:使用 patch-package(推荐)
适用场景:需持久化修改且 兼容 npm/yarm 项目
实现步骤:
- 安装工具:
npm install patch-package --save-dev
- 修改 node_modules :直接修改目标包源码中的变量
- 生成补丁文件
npx patch-package package-name # 生成 patches/package-name+version.patch
- 配置自动应用补丁:在package.json 中添加 postinstall 脚本:
"scripts": {
"postinstall": "patch-package"
}
- 提交补丁文件:将 patches 目录提交到版本控制。优点:
- 修改可持续化,团队写作时自动同步补丁
- 支持跨平台:注意:需锁定依赖版本,升级包版本后需重新生成补丁
三. Fork 源码 并发布私有包
适用场景:需要长期维护修改或扩展功能。
实现步骤:
1. Fork 原仓库:在 GitHub 等平台Fork 目标包源代码
2. 修改变量并提交:
3. 发布到 私有 registry
4. 工程中替换依赖
四. 代理模式(warpper)
**适用场景:**仅需覆盖少量变量或方法
实现方式:
- 创建代理模块
import originalPkg from 'original-pkg';
// 覆盖原包变量
originalPkg.targetVariable = 'new-value';
export default originalPkg;
2.在工程中引入代理
import pkg from './wrappers/package-wrapper';
优点:无需修改 node-modules,代码可读性高
缺点:不适用于深层嵌套的变量或者复杂逻辑
五.环境变量注入(动态配置)
适用场景:通过外部参数控制包行为
实现方式:
- 安装时传递参数
npm install --custom-var=value
- 包内读取环境变量
// 包源码中读取
const value = process.env.npm_config_custom_var;
注意:需原包支持动态配置,否则需结合其他方案修改源码
总结:方案选择指南
场景 | 推荐方案 | 工具链推荐 |
---|---|---|
快速测试 | 直接修改 node_modules | 所有包管理器 |
团队协作/持久化修复 | patch-package | npm/yarn |
长期维护或企业级私有化 | fork 源码 | 所有包管理器 |
轻量级涵盖变量 | 代理模式 | 所有包管理器 |
注意:
- 修改第三方包可能违反其许可证协议,需确认合法性
- 优先提交 PR 或 Issue 给原仓库,推动社区修复
33. Preload 和 Prefetch?
维度 | Preload | Prefetch |
---|---|---|
核心目的 | 当前页面关键资源加速 | 未来导航资源预加载 |
加载时机 | 立即高优先级加载 | 空闲时低优先级加载 |
使用资源 | CSS、字体、核心脚本 | 下一页 HTML、非关键脚本 |
缓存优先级 | 高 | 低 |
执行控制 | 需显示调用 | 需显示调用 |
页面跳转影响 | 请求终止 | 可能继续加载 |
最佳实践建议:
- Preload 用于关键路径资源:如首屏 CSS 和字体,通过 < link rel=“preload” as=“style”> 声明
- Prefetch 用于预测性加载:如用户可能访问得下一页资源,避免过早占用带宽
维度 | defer | async | Preload | Prefetch |
---|---|---|---|---|
核心目标 | 延迟执行依赖 DOM 的脚本 | 异步执行的独立脚本 | 加速当前页面关键资源加载 | 预加载未来可能需要的资源 |
加载优先级 | 中 | 中 | 高(由 as 决定) | 低 |
执行顺序 | 按文档顺序 | 无序 | 不执行,需显示调用 | 不执行,需显式调用 |
适用场景 | 框架初始化、DOM 操作 | 统计分析、广告脚本 | 字体、首屏、CSS、核心脚本 | 下一页 HTML、非首屏图片 |
34. JS 沙箱有哪些?
一.浏览器环境中的沙箱
-
iframe 沙箱
- 原理: 通过 < iframe> 的 sandbox 属性控制限制子页面的行为(如禁用脚本、表单提交等),结合 postMessage 通信机制实现数据交互
- 特点
- 天然隔离,支持权限精细化配置(如 allow-script、allow-same-origin)
- 腾讯无界框架(Wujie )基于此实现,通过劫持 document 和 location 将子应用 DOM 渲染到主界面
- 限制: 需配置复杂的安全策略(如 CSP),且无法直接访问主页面全局对象
-
Proxy + with + Function
- 原理: 利用 with 语句将代码作用域绑定到沙箱对象,通过 Proxy 拦截属性访问,防止逃逸到全局环境
- 实例:
function createSandbox(code, sandbox) { const fn = new Function('sandbox', `with(sandbox) { return (${code}) }`); const proxy = new Proxy(sandbox, { has: () => true, // 欺骗代码,使其认为所有属性已存在 get: (target, key) => key === Symbol.unscopables ? undefined : target[key] }); return fn(proxy); }
- 优点: 轻量级,适用于动态执行简单脚本
- 缺点: 可能被 Symbol.unscopables 绕过,需额外处理
-
shadowRealm 提案
- 原理: ECMAScript 新提案,提供独立的全局作用域,内置对象与原环境隔离
- 特点
- 标准化的沙箱机制,支持模块化加载
- 目前浏览器支持有限,尚未广泛应用
二. Node.js 环境的沙箱
-
vm 模块
- 原理: Node.js 内置模块,通过创建独立的上下文(context)隔离代码
- 示例:
function createSandbox(code, sandbox) { const fn = new Function('sandbox', `with(sandbox) { return (${code}) }`); const proxy = new Proxy(sandbox, { has: () => true, // 欺骗代码,使其认为所有属性已存在 get: (target, key) => key === Symbol.unscopables ? undefined : target[key] }); return fn(proxy); }
- 缺点:易通过原型链逃逸(如 this.constructor.constructor(‘return process’)() 获取 process 对象)
-
vm2 模块
- 原理: 基于 vm 模块增强,使用 Proxy 和 eval 隔离全局对象,拦截危险操作(如 require)
- 特点:
- 禁止访问 process 、fs等 敏感模块
- 支持异步代码超时控制,安全性显著高于 vm
- 示例:
const { VM } = require('vm2'); new VM().run('this.constructor.constructor("return process")()'); // 抛出异常
-
Safeify
第三方库:针对 Node.js 设计,结合进程隔离和资源限制,防止恶意代码导致内存泄漏或崩溃
三.特定场景的沙箱
-
小程序容器技术
- 原理:在微信、支付宝等平台中,通过封闭的运行环境和权限控制(如限制网络请求、文件读写)保护宿主应用安全
- 代表方案:
- 微信小程序:使用 WebView 隔离 + 自定义 API 白名单
- FinClip:支持私有化部署的小程序容器,提供跨平台(IOS/Android/Windows)沙箱环境
-
Web Worker
- 原理:在独立线程中运行脚本,无法访问 DOM 和主线程全局变量
- 限制:仍可调用部分 API (如 fetch ),需配合 CSP 增强安全
总结:
JS 沙箱的选择需根据场景权衡安全性与性能:
- 简单脚本:浏览器中可用 Proxy + with ,Node.js 优先选择 vm2
- 复杂隔离需求:浏览器用 iframe,Node.js 考虑 Safeify 或容器化(如 Docker)
- 企业级应用:小程序容器或自研沙箱框架(如腾讯无界)
35.HTML 中的 < meta> 标签?
HTML 中的 < meta>标签用于定义文档的元数据(metadata ),提供与页面相关的隐藏信息,如字符编码、页面描述、关键词、视口设置等
一、基础属性与常见用法
-
字符编码声明:通过charset 属性指定文档字符编码,确保多语言正确显示
<meta charset="UTF-8"> <!-- HTML5 推荐写法 --> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <!-- 旧版兼容写法 -->
作用:定义页面使用的字符集(如 UTF-8、GBK)
-
页面描述与 SEO 优化
-
description :提供页面内容的简短描述,影响搜索引擎结果页(SERP)的展示
<meta name="description" content="关于 HTML meta 标签的详细指南">
-
keywords: 列出与页面相关的关键词(现代搜索引擎对其以来降低,但仍可用于某些场景)
<meta name="keywords" content="HTML, meta标签, 前端开发">
-
author :声明作者
<meta name="author" content="张三">
-
robots :控制搜索引擎爬虫行为,如禁止索引或跟踪链接
<meta name="robots" content="noindex, nofollow"> <!-- 阻止索引和跟踪 -->
-
最佳实践:description 建议控制在 150 字符内,keywords 避免堆砌
-
二.浏览器兼容与渲染控制
- 视口(Viewport)设置
针对移动端适配,定义可视区域宽度、缩放比例等<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
- 参数说明:
- width=device-width:视口宽度与设备屏幕一致
- inital-scale=1.0 :初始缩放比例为 1
- maximum-scale=1.0 : 禁止用户手动放大
- **作用:**确保页面在移动端上自适应显示
-
IE 兼容模式:强制 IE 浏览器使用最新渲染引擎
<meta http-equiv="X-UA-Compatible" content="IE=edge">
扩展:若需支持 Chrome Frame 插件(已弃用),可添加 chrome=1
三.HTTP 指令模式(http-equiv)
-
页面刷新与重定向:定时刷新或跳转到新页面
<meta http-equiv="refresh" content="5; url=https://example.com"> <!-- 5秒后跳转 -->
应用场景: 临时维护页、旧域名迁移
-
缓存控制:设置页面过期时间或禁用缓存
<meta http-equiv="expires" content="Mon, 12 May 2025 00:00:00 GMT"> <!-- 过期时间 --> <meta http-equiv="pragma" content="no-cache"> <!-- 禁用本地缓存 --> <meta http-equiv="cache-control" content="no-store"> <!-- 禁止缓存存储 -->
注意: 现代浏览器更依赖 HTTP 头部(如 Cache-Control),但 meta 标签可作为补充
四.高级应用与扩展协议
-
社交媒体优化(Open Graph/Twitter Cards) : 定义在社交平台分享时的标题、描述和缩略图:
<!-- Open Graph 协议 --> <meta property="og:title" content="页面标题"> <meta property="og:description" content="页面描述"> <meta property="og:image" content="https://example.com/image.jpg"> <!-- Twitter 卡片 --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:site" content="@username">
作用: 增强链接在社交媒体中的展示效果
-
主题色与 PWA 支持: 设置浏览器地址栏颜色或启动渐进式 Web 应用(PWA)功能:
<meta name="theme-color" content="#ffffff"> <!-- 主题色 --> <meta name="apple-mobile-web-app-capable" content="yes"> <!-- 全屏模式 --> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <!-- 状态栏样式 -->
作用: : 移动端 WebApp 开发
五. 其他实用属性
-
禁止自动识别格式: 阻止移动端自动将数字识别为电话号码
<meta name="format-detection" content="telephone=no">
-
安全策略: 设置内容安全策略(CSP)或禁止不安全内容混合加载
<meta http-equiv="Content-Security-Policy" content="default-src 'self'"> <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
作用: CSP 通常通过 HTTP 头部实现,但某些场景可用 meta 标签
六. 注意事项与最佳实践
- 优先级冲突: HTTP 头部设置会覆盖 meta 标签(如 Content-type)
- 性能优化: 静态资源长期缓存可通过文件名哈希(如 app.[hash].js)实现,而非依赖 max-age
- SEO 陷阱:避免滥用 keywords 或 重复堆砌,可能触发搜索引擎惩罚
总结: < meta> 标签时 html 中不可或缺的元数据工具,涵盖从基础编码到高级 SEO、移动适配等多场景。合理使用或可显著提升兼容性、用户体验和搜索引擎排名