面试问答
面试问答,
别想转行了,自己的行业都干不了,别的行业也干不了
# 1. 都使用过哪些框架写项目
常用过的前端框架包括:Vue.js、React、Angular。后端框架有:Express、Koa、Django、Spring Boot 等,针对项目的需求选择合适的框架。
# 2. 对 Vue 的数据双向绑定怎么理解
Vue 实现数据双向绑定主要通过 v-model 指令和 Object.defineProperty(Vue 2)或 Proxy(Vue 3)。它使得数据的变化能够自动反映到视图层,反之,用户的输入也能更新数据。这是通过 Vue 的响应式系统来实现的,当数据变化时,会触发视图更新。
# 3. 你对 Vue 的 MVVM 有什么理解
MVVM(Model-View-ViewModel)是 Vue 的架构模式。它将应用程序的视图(View)与业务逻辑(Model)分离,ViewModel 是一个连接 View 和 Model 的桥梁,处理视图和数据之间的同步。Vue 通过 data 和计算属性等来实现 ViewModel 的功能,确保数据变化能够自动驱动视图更新。
# 4. JS 的事件循环机制说一下
JavaScript 是单线程执行的,但事件循环机制使得它可以处理异步操作。事件循环的核心概念是:
- 调用栈:执行当前任务。
- 任务队列:保存待执行的异步任务。
- 事件循环:在调用栈为空时,事件循环会将任务队列中的任务推入调用栈,执行它们。
通过这机制,JavaScript 可以处理如 setTimeout、Promises 等异步操作。
# 5. H5 那边的节流和防抖
- 防抖:防止短时间内多次触发事件,只有事件停止触发一段时间后才会执行。常用于用户输入或窗口调整大小等场景。
- 节流:限制单位时间内事件触发的次数,常用于高频率触发的事件,如滚动、resize 等。节流保证在一定时间内事件只会被执行一次。
# 6. **你能大概的说一下 Vue 的生命周期吗
| 选项式 API (Vue2) | 组合式 API (Vue3 setup) | 触发时机 | 常用场景 |
|---|---|---|---|
beforeCreate | 不需要 | 组件初始化完成,data 观测之前 | 几乎不用 (setup 中直接写代码即可) |
created | 不需要 | 组件初始化完成,data 观测之后 | 几乎不用 (setup 中直接写代码即可) |
beforeMount | onBeforeMount | 挂载开始前,DOM 还没生成 | 极少用 |
mounted | onMounted | DOM 渲染完成 | 最常用:发送请求、操作 DOM、初始化图表 |
beforeUpdate | onBeforeUpdate | 数据更新,视图未更新 | 监听数据变化前后的状态 |
updated | onUpdated | 数据 + 视图 都更新完成 | 处理更新后的 DOM 操作 |
beforeUnmount | onBeforeUnmount | 组件卸载(销毁)之前 | 最常用:清除定时器、解绑事件、取消订阅 |
unmounted | onUnmounted | 组件卸载完成 | 清理遗留资源 |
# 7. 跨域这边你们前端是如何解决的
前端跨域问题可以通过以下几种方式解决:
- CORS:通过服务器设置响应头允许跨域访问。
- JSONP:通过动态加载 script 标签来绕过同源策略。
- 代理服务器:通过设置开发环境的代理服务器,将请求代理到不同域上。
- iframe + postMessage:通过嵌套
iframe进行跨域通信。
# 8. ES7 里面 async/await 有使用过吗
是的,async/await 是 ES7 引入的异步编程语法,使用它可以避免回调地狱,写出更简洁的异步代码。async 标记一个函数为异步,await 用于等待 Promise 解析结果。
async function fetchData() {
try {
const response = await fetch("/data");
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
2
3
4
5
6
7
8
9
# 9. 你能讲一个你比较熟悉的项目, 大致说一下它的业务
可以讲述一个熟悉的项目,假如是开发一个新闻信息流广告平台,主要业务是通过新闻内容匹配相关广告,向用户展示个性化广告。这涉及到广告的精准投放、用户行为分析、后台管理系统等多个模块。
# 10.性能优化
- 构建层面
- Tree Shaking
- 代码分割(Code Splitting)大代码块分割为多个小代码块,实现懒加载
- 代码的压缩混淆
- 代码层面
- 关键资源优先加载,非关键的异步加载
- 减少重排 (Reflow) 与重绘 (Repaint),批量修改 DOM,动画,优先使用 CSS 的
transform和opacity属性,因为它们可以利用 GPU 加速,避免触发重排 - 事件处理优化,对于
scroll、resize、input等高频触发的事件,使用防抖 (Debounce) 和节流 (Throttle) 技术来控制事件处理函数的执行频率
- 资源层面
- 图片懒加载
- CDN
- gzip
- 使用现代图片格式,webP,图片压缩,字体压缩
- 合并小图标,使用字体文件等
- 开启浏览器缓存,(强缓存:强缓存是性能最优的缓存方式。它的核心逻辑是:浏览器在判断缓存有效后,会直接使用本地资源,完全不向服务器发送任何请求。协议缓存:当强缓存失效(例如
max-age过期)或被禁用(设置了no-cache)时,浏览器会进入协商缓存流程。它的核心逻辑是:浏览器会向服务器发送一个请求,询问“我本地的资源是否还有效?)
# 11.var,let,const
var是函数作用域:它只关心函数边界,不关心{}块边界(除非是函数块)。let和const是块级作用域:它们只在大括号{}内有效(如if、for循环)。
# 12.setup 的作用
- **自动暴露**:顶层声明的变量、函数等会自动在模板中可用,无需 `return`。
- **更简洁**:减少了大量样板代码。
- **更好的 TypeScript 支持**。
- **this执行underfined**
2
3
4
5
# 13.开发过程中遇到的问题,你是如何解决的
- 浏览器兼容性问题(使用 Babel 将 ES6+ 代码转译为 ES5,配合
core-js进行 Polyfill,填补旧浏览器的功能缺失) - 性能优化问题 (首屏加载慢,滚动/操作卡顿,内存泄漏)
- 移动端与响应式适配
- 网络请求与安全 (跨域问题 ,安全漏洞,弱网请求失败;设置超时时间)
# 14.事件循环
js 是一个单线程的机制,事件循环负责调度,防止页面卡死
| 宏任务 (Macro Task) | setTimeout, setInterval, I/O 操作, UI 渲染 | 耗时相对较长,优先级较低 |
|---|---|---|
| 微任务 (Micro Task) | Promise.then/catch/finally, queueMicrotask, async/await | 耗时短,优先级非常高 |
console.log('1. 同步代码 start'); // 同步代码
setTimeout(() => {
console.log('2. setTimeout 回调'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise.then 回调'); // 微任务
});
console.log('4. 同步代码 end'); // 同步代码
2
3
4
5
6
7
8
9
10
11
- 执行全局脚本(宏任务)
- 首先执行同步代码,打印
1. 同步代码 start。 - 遇到
setTimeout,将其回调函数放入宏任务队列,等待下一轮循环。 - 遇到
Promise.resolve().then(),将其回调函数放入微任务队列。 - 继续执行同步代码,打印
4. 同步代码 end。
- 首先执行同步代码,打印
- 清空微任务队列
- 当前宏任务(全局脚本)的同步代码执行完毕。
- 事件循环检查微任务队列,发现有一个
Promise回调,立即执行它,打印3. Promise.then 回调。 - 微任务队列被清空。
- 尝试渲染
- 浏览器会尝试进行一次 UI 更新(如果有的话)。
- 执行下一个宏任务
- 事件循环从宏任务队列中取出
setTimeout的回调并执行,打印2. setTimeout 回调。
- 事件循环从宏任务队列中取出
因此,最终的输出顺序是:1 → 4 → 3 → 2。
# async/await 与事件循环
async/await 是 Promise 的语法糖,它让异步代码看起来像同步代码。理解它的关键在于 await 关键字。
await 表达式会暂停 async 函数的执行,等待 Promise 完成。await 后面的代码,会被当作一个微任务(类似于 .then() 的回调)来处理。
async function asyncFn() {
console.log('a. async 函数开始');
await Promise.resolve(); // 等待一个 Promise
console.log('b. await 之后的代码'); // 这部分代码是微任务
}
console.log('1. 同步代码 start');
asyncFn();
console.log('2. 同步代码 end');
2
3
4
5
6
7
8
9
# 15.节流/防抖
两者都依赖闭包来持久化状态(如定时器 ID 或上次执行时间)。
# 节流:
固定时间执行一次。(多用于按钮)
追问:
- 立即执行版本怎么实现?
- Vue 场景怎么用?
节流有多种实现方式,这里介绍最常用的时间戳版本,它通过比较当前时间和上次执行时间来判断是否执行函数。
/**
* 节流函数 (时间戳版)
* @param {Function} fn - 需要执行的函数
* @param {Number} interval - 间隔时间(毫秒)
* @returns {Function} - 包装后的函数
*/
function throttle(fn, interval) {
let lastTime = 0; // 通过闭包保存上次执行的时间戳
return function(...args) {
const context = this;
const now = Date.now();
// 如果当前时间与上次执行时间的差值大于等于间隔,则执行
if (now - lastTime >= interval) {
fn.apply(context, args);
lastTime = now;
}
};
}
// 使用示例:页面滚动加载
const handleScroll = throttle(() => {
console.log('检查是否加载更多内容');
}, 200);
window.addEventListener('scroll', handleScroll);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 防抖:
触发后延迟执行。事件持续触发时,只在最后一次触发后执行(多用于输入框)
# 防抖 (Debounce) 实现
防抖的核心是维护一个定时器,每次事件触发时都清除上一次的定时器并重新设置。
/**
* 防抖函数
* @param {Function} fn - 需要执行的函数
* @param {Number} delay - 延迟时间(毫秒)
* @returns {Function} - 包装后的函数
*/
function debounce(fn, delay) {
let timer = null; // 通过闭包保存定时器ID
return function(...args) {
const context = this;
// 清除上一次的定时器,重新计时
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
}
// 使用示例:搜索框输入
const searchInput = document.getElementById('search');
const handleSearch = debounce((e) => {
console.log('发送搜索请求:', e.target.value);
}, 500);
searchInput.addEventListener('input', handleSearch);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 16.vue3 响应式原理
使用 es6 的 proxy 构建
| 特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 监听对象 | 只能劫持对象的已有属性 | 代理整个对象,可拦截 13 种操作 |
| 新增/删除属性 | 无法检测,需使用 Vue.set / Vue.delete | 自动检测,原生支持 |
| 数组索引/长度 | 需特殊处理(重写变异方法) | 原生支持,无需特殊处理 |
| 性能 | 初始化时递归遍历所有属性,开销大 | 懒代理,访问时才代理嵌套对象,性能更优 |
- Proxy (拦截器)
- 它是响应式系统的核心。
reactive函数会创建一个Proxy来包裹原始数据对象。 - 它负责拦截对数据的所有操作,最核心的是
get(读取) 和set(修改) 操作。
- 它是响应式系统的核心。
- Reflect (反射器)
- 它是一套与
Proxy拦截器方法对应的工具函数。 - 在
Proxy的拦截器内部,使用Reflect.get或Reflect.set来执行对原始对象的默认操作。这样做可以确保this指向的正确性,并保留原生对象的行为。
- 它是一套与
- Effect (副作用函数)
- 它代表了依赖响应式数据的代码,例如组件的渲染函数、
watch的回调函数等。 - 当
effect执行时,它会读取响应式数据,从而触发依赖收集。当数据变化时,effect会被重新执行,从而更新视图或执行相应逻辑。
- 它代表了依赖响应式数据的代码,例如组件的渲染函数、
# 工作流程:依赖收集与触发更新
整个响应式系统的工作流程是一个完美的闭环,分为两个关键阶段:
# 1. 依赖收集 (Track)
这个过程发生在读取响应式数据时。
- 当一个副作用函数 (effect) 执行时(例如组件首次渲染),它会访问响应式对象的属性。
- 这个访问操作会触发
Proxy的get拦截器。 - 在
get拦截器中,Vue 会调用track函数,记录下“当前正在执行的 effect”依赖于“这个属性”。 - 这个依赖关系会被存储在一个“依赖映射表”中,建立
数据属性 -> effect的关联。
# 2. 触发更新 (Trigger)
这个过程发生在修改响应式数据时。
- 当响应式对象的属性被修改(或新增、删除)时,会触发
Proxy的set(或deleteProperty) 拦截器。 - 在拦截器中,Vue 会调用
trigger函数。 trigger函数会根据被修改的属性,从之前建立的“依赖映射表”中找到所有依赖于该属性的 effect。- 最后,将这些 effect 重新执行一遍,从而实现视图的自动更新。
# Add cloudflare gpg key
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-public-v2.gpg | sudo tee /usr/share/keyrings/cloudflare-public-v2.gpg >/dev/null
# Add this repo to your apt repositories
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-public-v2.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
# install cloudflared
sudo apt-get update && sudo apt-get install cloudflared
2
3
4
5
6
7
8
9
http://113.207.49.134:22066/api/v1/client/subscribe?token=801e0f0d1e8c4a1b8ea658c2370aca87
# 17.UniApp 的 JS 能力有限,如果业务需要调用原生功能(如复杂的蓝牙交互或特定的 SDK),你会怎么做?
我会选择开发或集成“原生插件”。核心是利用 UniApp 提供的桥接层。JS 调用 uni.requireNativePlugin 获取原生模块实例,然后调用其方法。
# 18.Promise
# Promise.all() —— “全员通过才算过”
- 逻辑:接收一组 Promise,只有当所有 Promise 都成功时,才返回成功结果(数组);只要有一个失败,就立即返回失败。
# Promise.race() —— “谁快听谁的”
- 逻辑:接收一组 Promise,第一个改变状态(无论成功或失败)的 Promise 决定最终结果。
# Promise.allSettled() —— “不管成败,我都要结果”
- 逻辑:等待所有 Promise 完成,返回一个包含所有结果(状态和值/原因)的数组。
# Promise.any() —— “只要有一个人成功就行”
- 逻辑:只要有一个 Promise 成功,就立即返回成功;只有当所有 Promise 都失败时,才返回失败。
# 19.数组的常用方法
# 1. 增删改查(操作方法)
这类方法主要用于改变数组的内容。
# 增(添加)
push(item1, ...):在数组末尾添加元素。返回新数组的长度。unshift(item1, ...):在数组开头添加元素。返回新数组的长度。concat(arr1, ...):合并数组。不修改原数组,返回一个新数组。
# 删(删除)
pop():删除并返回数组最后一个元素。shift():删除并返回数组第一个元素。
# 改(增删改一体)
splice(start, deleteCount, ...items):这是数组的“手术刀”。
start:开始位置。deleteCount:删除多少个(0 表示不删)。items:要插入的新元素。- 注意:它会修改原数组,返回被删除的元素数组。
# 查(查找)
indexOf(item):返回元素索引,找不到返回 -1。includes(item):返回true或false,判断是否存在。slice(start, end):截取数组片段。不修改原数组,返回新数组(常用于实现浅拷贝)。
# 2. 排序与填充
sort(compareFn)- 修改原数组。
- 坑:默认按字符编码排序(
[10, 5, 40, 25]会变成[10, 25, 40, 5])。 - 解法:必须传入比较函数,如
arr.sort((a, b) => a - b)进行升序排列。
reverse():反转数组顺序,修改原数组。fill(value):用固定值填充数组,修改原数组。
# 3. 迭代方法(高阶函数)
这是现代开发(尤其是 React/Vue)中最常用的部分,它们不会修改原数组(除了回调函数内部手动修改对象属性外)。
# forEach —— 纯遍历
- 用途:执行副作用(如打印日志、操作 DOM、修改外部变量)。
- 返回值:
undefined。 - 注意:无法使用
break或return中断循环。
# map —— 映射/转换
- 用途:将数组中的每一项转换成新格式。
- 返回值:新数组(长度与原数组一致)。
- 场景:
[1, 2, 3]→[2, 4, 6]或 对象数组提取 ID 列表。
# filter —— 过滤
- 用途:筛选符合条件的项。
- 返回值:新数组(长度 ≤ 原数组)。
- 场景:删除列表中的无效数据,或搜索功能。
# reduce —— 归并/汇总
- 用途:将数组“压缩”成一个值(数字、对象、数组等)。
- 语法:
arr.reduce((累加器, 当前值) => { ... }, 初始值)。 - 场景:数组求和、计算最大值、将数组转为对象(分组)。
# 判断与查找
find(callback):返回第一个符合条件的元素。findIndex(callback):返回第一个符合条件的索引。some(callback):只要有一个符合条件就返回true(类似“或”逻辑)。every(callback):所有都符合条件才返回true(类似“且”逻辑)。
# 20.闭包
函数 + 它能访问的作用域环境。
一般是一个函数返回了一个函数的情形,
- 返回的函数,仍然可以 i 访问外层函数的局部变量即使外层函数已经执行结束
- 这种访问保持了外层变量的状态,可以实现私有变量或累加器等功能
- 并不是所有返回函数的场景都是闭包,关键是返回的函数引用了外层函数的变量
闭包
常见应用:
- 防抖节流
- 模块封装
- 私有变量
# 21.uniapp 开发小程序遇到的坑
1.vue2和vue3区别;:style="[object]",在vue2中识别成这样的了, 改成 :style="[styleObj]"
2.ios和安卓的区别;ios时间只能识别“/”,需要把“-”转换为“/”
3.字体渲染差异,需要统一字体,设置line-height:1.5
4.录音格式兼容,ios默认aac,安卓默认silk
5.ios自带bounce,下拉页面留白
2
3
4
5
6
# 22.项目中对接了 ai,你都遇到什么问题
ai 输出的内容不可控,格式不规范,内容可能逻辑不通,慢,需要进行
prmopt 优化;
定义角色,说明任务,禁止什么,给出示例模版
严格的 json 教研,自动补全,加上流式打字机效果,
# 23.设计基于 RBAC 的权限模型,实现页面权限 + 按钮权限 + 数据权限三层控制
页面路由:静态路由和动态路由(通过 addRouter 添加进去);有可能刷新丢失,在路由守卫 beforeEach 里面判断是否添加了权限
按钮权限:使用自定义指令;按钮绑定权限码(add,edit)等,起 pinia 里面判断权限数组是否包含
数据权限:前端只要是传递 scope 参数,后端数据过滤
# 24.对外平台采用“多租户数据隔离”思路,所有接口均基于合作商唯一标识进行数据过滤,避免跨 租户访问风险
所有接口统一通过添加合作商唯一 id 进行强制数据过滤,放置数据安全
# 25.优化生成性能与交互体验,减少生成等待时间
简化 prompt,流式返回 sse,websocket,骨架屏,防抖防止重复点击,异步加载,离开页面
# 26.虚拟列表
如果是定高列表很好处理,但在实际业务中(比如评论区),高度往往是不固定的。我的处理策略是**‘预估 + 测量 + 缓存’**:
- 预估:先给所有未渲染的项设置一个默认高度(比如 50px),保证初始滚动条能显示。
- 测量:当数据渲染上屏后,利用
ResizeObserver或getBoundingClientRect获取它的真实高度。 - 缓存与修正:把真实高度存到一个缓存数组里(
heightCache[index] = realHeight)。下次滚动计算位置时,优先使用缓存的高度。
这样可以保证滚动过程中,位置计算越来越精准,同时避免了重复测量带来的性能损耗。”
性能和优化
- 滚动性能优化:滚动事件触发频率极高,我会使用
requestAnimationFrame对滚动回调进行节流,确保每帧只计算一次位置,保证 60fps 的流畅度。 - 定位查找优化:如果是超长列表(如 10 万条),计算
startIndex时如果遍历高度数组会很慢。我会维护一个累积高度数组,利用二分查找来快速定位当前滚动位置对应的索引,将时间复杂度从 O(N) 降到 O(logN)。 - DOM 复用:在框架(如 Vue/React)中,我会确保
key的稳定性,或者使用对象池的思想复用 DOM 节点,减少 Diff 算法的开销。”
# 27.http 请求三次握手,四次挥手
作用:建立可靠的 TCP 连接,确保双方收发能力正常。 第一次握手(SYN) 客户端 → 服务端:发送 SYN 包,请求建立连接。 第二次握手(SYN+ACK) 服务端 → 客户端:回复 SYN+ACK,同意连接并确认。 第三次握手(ACK) 客户端 → 服务端:回复 ACK,连接建立成功。 核心答案:客户端发 SYN,服务端回 SYN+ACK,客户端再回 ACK,三次握手完成,TCP 连接建立。
第一次挥手(FIN):主动方发 FIN,请求关闭。 第二次挥手(ACK):被动方回 ACK,确认收到。 第三次挥手(FIN):被动方发 FIN,同意关闭。 第四次挥手(ACK):主动方回 ACK,连接关闭。
# 28.浏览器渲染页面的流程
- DNS 解析:域名 → IP 地址。
- 建立 TCP 连接:三次握手。
- TLS 握手(HTTPS):加密通道建立。
- 发送 HTTP/HTTPS 请求。
- 服务端处理并返回响应。
- 解析 HTML,构建 DOM 树。
- 解析 CSS,构建 CSSOM 树。
- 合成 Render Tree(渲染树)。
- 布局(Layout/Reflow):计算位置大小。
- 绘制(Paint):绘制像素。
- 合成(Composite):图层合并,页面展示。
- 关闭 TCP 连接:四次挥手。
# 29.开发中遇到印象最深的问题
# 30.调试起来最麻烦的一个bug
在table中使用表格,span里面的空格无法自动去除,因为vxe-table中设置了一个样式:white-space:pre-line;最后实现方法:options.compilerOptions.whitespace="condense"
form表单内数字的输入框type=number,页面滑动的时候会触发他的值的变化,因为vxe-table自动添加了一个throller 节流导致,修改方法就是@wheel.native.prevent,去除滚轮的原生事件,或者是不使用ei-input,使用el-number-input
页面上有
<>这种符号的情况,会转义横向滚动条不好用,2.7的bug,更新到2.10就好了,因为源码中在处理纵向无限滚动的时候,把源码中的动态的宽高设置成了固定的高度
表格在某一个宽度的时候会出现抖动,情况复现:就是最后一列操作是宽度固定的,前面的宽度都是自适应的,elemetui设置的最小默认值是80,除了第一列,其他的列是平均分的,除了第一列其他的都是向下取整,会导致第一列的宽度变窄,会换行,然后导致页面出现纵向滚动条,这个时候许呀哦重新计算每一列的宽度,计算之后第一列的宽度又变大了
# 1. ElementUI Table 列宽分配规则(你遇到的版本)
最后一列:固定宽度(如 120px)
其他列:不设 width,只受 min-width=80 约束
剩余宽度分配逻辑:
- 总可用宽度 = 表格宽度 − 固定列宽
- 动态列按 min-width 权重 分配剩余空间
- 除第一列外,其他列宽度都向下取整
- 最后把 剩下的零头全部给第一列
结果:
- 中间列都是整数宽度
- 第一列宽度是小数 / 不固定
# 2. 抖动触发链(关键)
临界宽度:表格缩到某一宽度
第一列过窄 → 内容换行 → 行高变高 → 出现纵向滚动条
滚动条出现 → 表格可用宽度变少(减去滚动条 15~17px)
ElementUI 检测到宽度变化 → 重新计算列宽
重新计算后:
- 可用宽度变多一点
- 第一列分到更多零头 → 变宽 → 不换行 → 行高恢复 → 滚动条消失
滚动条消失 → 可用宽度又变大 → 再次触发重算 → 第一列又变窄 → 换行 → 滚动条出现
循环往复 → 视觉抖动
一句话:
滚动条的出现 / 消失 ↔ 列宽重算 ↔ 换行 / 不换行 → 死循环抖动
解决方法:
- :show-overflow-tooltip="true"
- padding-right:20px;预留出来滚动条的宽度
- 设置表格为fixed
# 31.在使用element组件中,有对源代码进行过修改吗
项目中不会直接修改 Element 源码,避免依赖不可维护、升级失效。
样式用 :deep () 深度选择器覆盖;
禁用 / 修改组件原生逻辑,通过组件事件 return false、封装组件、props 配置实现。
.native 只能监听根 DOM 原生事件,无法拦截组件内部逻辑,不能用来禁用组件原生方法,所以一般不用 native 做这件事。
# 32.Echarts踩坑
窗口缩放后图表变形、不自适应,监听resize,并且还要加防抖, 切换页面之后需要销毁实例
“在容器节点被销毁时,总是应调用 echartsInstance.dispose 以销毁实例释放资源,避免内存泄漏。”
onUnmounted(() => {
chart?.dispose()
chart = null
})
2
3
4
echarts legend头部有遮挡、上半部分显示不全,像被截取了一块;
legend-textStyle中加入lineHeight即可
不要用 ref 存实例,用 shallowRef
坑:
const chart = ref(null) → Vue 会深度 Proxy 监听 ECharts 巨大对象,巨卡。
import { shallowRef } from 'vue'
const chart = shallowRef(null)
只存引用,不做响应式劫持。
2
3
大数据量关闭动画、开启 large 模式、做数据降采样,用 Canvas 渲染。
采用按需引入 ECharts 模块,减少包体积与初始化开销。
# 33.WebSocket频繁更新echats,会有什么问题吗
节流 + 批量更新
- 把 WebSocket 数据先缓存,按 300~500ms 批量更新一次
限制数据长度(滑动窗口)
- 只保留最近 N 条数据,避免无限增长,限制MAX,从前删除数字
使用
appendData替代setOption- 减少 diff 开销
关闭或降低动画
- 提升实时场景下的流畅性
必要时做数据采样(sampling)
- 控制渲染点数量
在大屏项目中,由于 WebSocket 持续推送数据,如果不做控制会导致图表卡顿。 我们通过“采样 + 时间窗口”优化:
- 采样:减少数据点数量,比如按间隔或聚合处理
- 时间窗口:只保留最近 N 条数据,旧数据删除 同时配合节流更新和 ECharts 的增量更新,提高渲染性能。
大屏一般不会用全量数据,因为数据量过大不仅影响性能,而且在有限屏幕上也没有展示价值。通常会通过“窗口”控制数据范围,比如只保留最近一段时间的数据,同时通过“采样”减少渲染点数量,比如使用 ECharts 的 LTTB 算法,在保证趋势和峰值的前提下降低渲染压力,从而在性能和展示效果之间做平衡。
首屏加载优化
- 分批异步加载,不要同时占满主线程
- 只加载可视区域内的
减少一次性渲染压力 + 控制数据量 + 降低更新频率 + 提升用户感知速度
# 34.异步组件
<!-- 父页面:使用异步组件的页面 -->
<template>
<div class="page">
<h2>Vue3 异步组件(完整带注释版)</h2>
<!-- 按钮控制:控制异步组件是否渲染 -->
<button @click="show = true">点击加载图表组件</button>
<!--
异步组件使用:
1. 只有 show = true 时才会开始加载
2. 加载中自动显示 Loading
3. 加载失败自动显示 Error
4. 加载完成后触发 @loaded 事件
-->
<AsyncChart
v-if="show"
title="首屏加载优化图表"
@loaded="handleChartLoaded"
/>
</div>
</template>
<script setup>
import { defineAsyncComponent, ref } from 'vue'
// 1. 加载中组件(可以单独拆文件,这里写简易版)
const LoadingComponent = {
template: '<div style="color:#1677ff">组件加载中...</div>'
}
// 2. 加载失败组件
const ErrorComponent = {
template: '<div style="color:red">组件加载失败!</div>'
}
// ==============================================
// 3. 核心:定义异步组件(所有参数都在这里)
// ==============================================
const AsyncChart = defineAsyncComponent({
// --------------------------
// 必填:加载器(异步导入组件)
// 返回 import() 函数
// --------------------------
loader: () => import('./Chart.vue'),
// --------------------------
// 可选:加载中显示的组件
// --------------------------
loadingComponent: LoadingComponent,
// --------------------------
// 可选:加载失败显示的组件
// --------------------------
errorComponent: ErrorComponent,
// --------------------------
// 可选:延迟 200ms 再显示 loading
// 作用:加载快的话不闪屏
// 默认:200
// --------------------------
delay: 200,
// --------------------------
// 可选:超时时间(毫秒)
// 10 秒加载失败算错误
// --------------------------
timeout: 10000,
// --------------------------
// 可选:失败后是否支持重试
// --------------------------
retry: true,
// --------------------------
// 可选:配合<Suspense>使用
// 普通项目不用改
// --------------------------
suspensible: false
})
// 控制异步组件显示/隐藏
const show = ref(false)
// 监听:异步组件加载完成
function handleChartLoaded() {
console.log('✅ 异步组件加载 + 渲染完成!')
alert('图表组件加载成功!')
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 35.地图相关
新闻h5中对接过,高德的ip定位
支付宝小程序对接过周边查询
绘制轨迹图;
// 1. 初始化地图
const map = new AMap.Map('map', {
zoom: 14,
center: [121.4737, 31.2304] // 轨迹中心点
})
// 2. 运动轨迹经纬度数组(示例:一串点)
const path = [
[121.465, 31.235],
[121.468, 31.233],
[121.470, 31.231],
[121.473, 31.229],
[121.475, 31.227]
]
// 3. 绘制轨迹线
const polyline = new AMap.Polyline({
path: path,
strokeColor: '#1677ff', // 轨迹颜色
strokeWeight: 6, // 轨迹宽度
strokeOpacity: 0.8, // 透明度
zIndex: 50
})
polyline.addTo(map)
// 4. 标记起点(绿色)
new AMap.Marker({
position: path[0],
icon: 'https://webapi.amap.com/theme/v1/markers/markers_nolabel.png',
title: '起点',
map: map
})
// 5. 标记终点(红色)
new AMap.Marker({
position: path[path.length - 1],
icon: 'https://webapi.amap.com/theme/v1/markers/markers_red_nolabel.png',
title: '终点',
map: map
})
// 自动调整视野,包含整条轨迹
map.setFitView([polyline])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 36.vue降级处理
在 Vue3 项目中,我主要做过两类降级处理:
一是浏览器兼容降级,通过 Babel 转译、core-js 垫片、环境判断,保证老旧设备不白屏;
二是低性能设备与弱网降级,通过懒加载、虚拟滚动、关闭非必要动画、接口缓存等方式,保证基础功能可用;
同时对异常场景做容错降级,使用错误边界和接口兜底,避免局部问题影响整个页面。
# 37.遇到的最大的挑战
解构导致响应式丢失,要使用 toRefs包一下