1、css会阻塞html的渲染吗
在 CSS 与 HTML 的渲染关系中,CSS 会阻塞 HTML 的渲染,但不会阻塞 HTML 的解析,具体逻辑如下:
- CSS 阻塞渲染的原因浏览器在渲染页面时,需要先构建 CSSOM(CSS 对象模型) 和 DOM(文档对象模型) ,然后将两者结合生成 渲染树(Render Tree) ,才能进行页面渲染。如果 CSS 尚未加载或解析完成,CSSOM 不完整,浏览器无法确定元素的最终样式(例如尺寸、颜色、位置等),因此会暂停渲染过程,等待 CSSOM 构建完成后再继续。这意味着,若外部 CSS 文件加载缓慢,会导致页面长时间处于空白状态(解析仍在进行,但渲染被阻塞)。
- CSS 不阻塞 HTML 解析HTML 解析(构建 DOM)和 CSS 解析(构建 CSSOM)是 并行进行 的。浏览器在下载 CSS 的同时,会继续解析 HTML 生成 DOM 树,不会因为 CSS 未加载完成而停止解析 HTML。这种并行处理机制是为了提高效率,避免资源加载阻塞文档结构的解析。
- 特殊情况:CSS 阻塞 JS 执行虽然 CSS 不阻塞 HTML 解析,但会阻塞 JavaScript 执行。因为 JS 可能会操作 CSSOM(例如通过
document.styleSheets访问样式),浏览器为了保证 JS 执行时能获取完整的 CSSOM,会暂停 JS 执行,等待 CSS 解析完成。而 JS 执行被阻塞可能间接影响 HTML 解析(若 JS 代码在解析过程中插入 DOM 操作)。
总结:CSS 会阻塞页面渲染(等待 CSSOM 完成),但不阻塞 HTML 解析,同时可能间接影响 JS 执行,需注意优化 CSS 加载(如关键 CSS 内联、异步加载非关键 CSS)以减少渲染阻塞。
2、错误捕获怎么做
前端错误捕获是保障页面稳定性、提升用户体验的关键环节,需要覆盖同步错误、异步错误、资源加载错误、接口错误等多种场景。以下是具体实现方案:
一、同步代码错误:try/catch
适用于同步执行的代码块(如变量操作、函数调用等),直接捕获执行过程中抛出的错误。
javascript
运行
try {
// 可能出错的同步代码
const obj = null;
console.log(obj.name); // 抛出 TypeError
} catch (error) {
// 错误处理:打印日志、提示用户等
console.error("同步错误:", error);
// 错误信息包含:name(类型)、message(描述)、stack(堆栈)
reportErrorToServer({
type: error.name,
message: error.message,
stack: error.stack,
time: new Date().toISOString()
});
}
二、异步错误:分场景处理
1. Promise 错误
- 未使用
async/await时:用.catch()捕获 - 使用
async/await时:配合try/catch
javascript
运行
// 方式1:.catch() 捕获 Promise 错误
fetch("/api/data")
.then(res => res.json())
.then(data => { /* 处理数据 */ })
.catch(error => {
console.error("Promise 错误:", error);
});
// 方式2:async/await + try/catch
async function fetchData() {
try {
const res = await fetch("/api/data");
const data = await res.json();
} catch (error) {
console.error("async/await 错误:", error);
}
}
2. 定时器 / 事件回调错误
需在回调内部嵌套 try/catch,否则错误会冒泡到全局。
javascript
运行
setTimeout(() => {
try {
const arr = [1, 2, 3];
arr.forEach(item => item()); // 抛出 TypeError(数字不能作为函数调用)
} catch (error) {
console.error("定时器错误:", error);
}
}, 1000);
// 事件回调同理
document.getElementById("btn").addEventListener("click", () => {
try {
// 可能出错的代码
} catch (error) {
console.error("点击事件错误:", error);
}
});
三、全局错误捕获:兜底机制
当局部错误未被捕获时,通过全局事件监听兜底,避免错误直接暴露给用户(如页面白屏、控制台报错)。
1. 捕获同步和非 Promise 异步错误:window.onerror
触发时机:同步错误、定时器 / 事件回调等非 Promise 异步错误。
javascript
运行
window.onerror = function(message, source, lineno, colno, error) {
// 参数说明:
// message:错误信息
// source:错误发生的文件 URL
// lineno/colno:行号/列号
// error:错误对象(包含 stack 等详细信息)
console.error("全局错误:", error);
// 上报错误到服务端
reportErrorToServer({
message,
source,
line: lineno,
column: colno,
stack: error?.stack
});
return true; // 阻止错误默认行为(如控制台打印)
};
2. 捕获未处理的 Promise 错误:unhandledrejection
触发时机:Promise 被拒绝(reject)但未用 .catch() 或 try/catch 处理。
javascript
运行
window.addEventListener("unhandledrejection", (event) => {
// event.reason 是 Promise 拒绝的原因(错误对象或信息)
console.error("未处理的 Promise 错误:", event.reason);
reportErrorToServer({
type: "UnhandledPromiseRejection",
message: event.reason?.message || event.reason,
stack: event.reason?.stack
});
event.preventDefault(); // 阻止浏览器默认警告
});
四、资源加载错误:error 事件
图片、脚本、样式等资源加载失败时,会触发 error 事件,可通过监听捕获。
javascript
运行
// 监听单个资源
const img = new Image();
img.src = "invalid-image.png";
img.addEventListener("error", (event) => {
console.error("图片加载失败:", event.target.src);
reportErrorToServer({
type: "ResourceLoadError",
url: event.target.src,
tagName: event.target.tagName
});
});
// 全局监听资源加载错误(通过捕获阶段)
window.addEventListener("error", (event) => {
const target = event.target;
// 区分资源错误和 JS 执行错误(资源错误的 target 是元素节点)
if (target instanceof HTMLElement) {
console.error("全局资源加载错误:", target.src || target.href);
reportErrorToServer({
type: "ResourceLoadError",
url: target.src || target.href,
tagName: target.tagName
});
}
}, true); // 第三个参数为 true,在捕获阶段监听(资源错误不冒泡,需捕获)
五、接口请求错误:主动判断状态
除了 Promise 错误(如网络中断),还需处理 HTTP 错误状态(4xx、5xx 等)。
javascript
运行
async function request(url) {
try {
const res = await fetch(url);
if (!res.ok) {
// 主动抛出 HTTP 错误(状态码非 2xx)
throw new Error(`HTTP 错误:${res.status} ${res.statusText}`);
}
return await res.json();
} catch (error) {
console.error("接口请求错误:", error);
reportErrorToServer({
type: "RequestError",
url,
message: error.message,
status: error.status // 可自定义状态码
});
}
}
六、错误上报:服务端记录
捕获错误后,通常需要上报到服务端,用于监控和排查问题。上报方式:
javascript
运行
function reportErrorToServer(errorInfo) {
// 简单的上报方式:通过 img 标签的 src 发起 GET 请求(无跨域问题)
const img = new Image();
const params = new URLSearchParams({
...errorInfo,
pageUrl: window.location.href, // 当前页面 URL
userAgent: navigator.userAgent // 浏览器信息
});
img.src = `https://your-error-monitor.com/report?${params}`;
}
关键注意事项
- 避免错误吞噬:捕获错误后必须处理(如上报、提示),不要空
catch。 - 区分错误类型:针对不同错误(JS 执行、资源加载、接口)做差异化处理。
- 堆栈信息完整:错误对象的
stack属性包含调用栈,是排查问题的关键(注意生产环境可能被压缩,需配合 sourcemap 还原)。 - 跨域脚本错误:若页面引入跨域脚本,
window.onerror可能无法获取详细错误信息,需在脚本标签添加crossorigin属性,并确保服务端返回正确的 CORS 头。
3、网络请求错误如何捕获
网络请求错误的捕获需要覆盖网络层面异常(如断网、超时)和业务层面异常(如 HTTP 4xx/5xx 状态码),不同请求方式(XMLHttpRequest、fetch、axios 等)的处理方式略有差异,以下是具体实现:
一、原生 fetch 请求的错误捕获
fetch 是现代浏览器原生 API,默认不会将 HTTP 错误状态(如 404、500)视为 “错误”(仅网络失败时才会触发 catch),因此需要手动判断状态码。
完整处理示例:
javascript
运行
async function fetchData(url) {
try {
// 1. 捕获网络层面错误(如断网、跨域失败)
const response = await fetch(url, {
method: 'GET',
timeout: 5000, // 注意:fetch 本身不支持 timeout,需额外处理
});
// 2. 手动判断 HTTP 状态码(处理业务错误)
if (!response.ok) {
// response.ok 为 true 时,状态码在 200-299 之间
throw new Error(`请求失败:${response.status} ${response.statusText}`);
}
// 3. 解析响应数据(可能抛出 JSON 解析错误)
const data = await response.json();
// 4. 处理业务逻辑错误(如接口返回 code 非 0)
if (data.code !== 0) {
throw new Error(`业务错误:${data.message || '未知错误'}`);
}
return data;
} catch (error) {
// 统一捕获所有错误并处理
console.error('请求错误:', error.message);
// 错误上报
reportError({
type: 'fetchError',
url,
message: error.message,
stack: error.stack
});
// 给用户提示
alert(`请求失败:${error.message}`);
}
}
补充:fetch 超时处理
fetch 没有内置超时参数,需通过 AbortController 实现:
javascript
运行
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timer); // 清除超时定时器
// 后续处理同上...
} catch (error) {
if (error.name === 'AbortError') {
error.message = `请求超时(${timeout}ms)`;
}
// 错误处理...
}
}
二、XMLHttpRequest(XHR)的错误捕获
传统 XHR 请求通过事件监听处理错误,可覆盖网络错误、超时、状态码异常等。
javascript
运行
function xhrRequest(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.timeout = 5000; // 超时时间(ms)
// 1. 响应成功(状态码 200-299)
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
if (data.code !== 0) {
reject(new Error(`业务错误:${data.message}`));
} else {
resolve(data);
}
} catch (parseError) {
reject(new Error(`数据解析错误:${parseError.message}`));
}
} else {
// 2. HTTP 状态码错误(如 404、500)
reject(new Error(`请求失败:${xhr.status} ${xhr.statusText}`));
}
};
// 3. 网络错误(如断网、跨域失败)
xhr.onerror = () => {
reject(new Error('网络错误,无法连接服务器'));
};
// 4. 超时错误
xhr.ontimeout = () => {
reject(new Error(`请求超时(${xhr.timeout}ms)`));
};
xhr.send();
});
}
// 使用时捕获错误
xhrRequest('/api/data')
.then(data => console.log('成功:', data))
.catch(error => {
console.error('XHR 错误:', error.message);
// 错误上报...
});
三、axios(第三方库)的错误捕获
axios 是常用的 HTTP 客户端,内置了更完善的错误处理机制,可区分不同错误类型。
基本用法:
javascript
运行
import axios from 'axios';
// 全局配置超时
axios.defaults.timeout = 5000;
async function axiosRequest(url) {
try {
const response = await axios.get(url);
// 处理业务逻辑错误(接口返回 code 非 0)
if (response.data.code !== 0) {
throw new Error(`业务错误:${response.data.message}`);
}
return response.data;
} catch (error) {
// axios 错误分为请求错误和响应错误
let errorMsg = '';
if (error.response) {
// 1. 有响应但状态码异常(如 404、500)
errorMsg = `请求失败:${error.response.status} ${error.response.statusText}`;
} else if (error.request) {
// 2. 无响应(网络错误、超时等)
errorMsg = '网络错误,无法连接服务器';
} else if (error.code === 'ECONNABORTED') {
// 3. 超时错误
errorMsg = `请求超时(${axios.defaults.timeout}ms)`;
} else {
// 4. 其他错误(如业务逻辑抛错)
errorMsg = error.message;
}
console.error('Axios 错误:', errorMsg);
// 错误上报...
alert(`请求失败:${errorMsg}`);
}
}
进阶:axios 拦截器统一处理
通过拦截器全局捕获错误,避免重复代码:
javascript
运行
// 请求拦截器(可选)
axios.interceptors.request.use(
config => config,
error => Promise.reject(error)
);
// 响应拦截器(统一处理错误)
axios.interceptors.response.use(
response => {
// 响应成功时,先判断业务 code
if (response.data.code !== 0) {
return Promise.reject(new Error(`业务错误:${response.data.message}`));
}
return response.data;
},
error => {
// 统一处理 HTTP 错误和网络错误
let errorMsg = '';
if (error.response) {
errorMsg = `HTTP ${error.response.status}:${error.response.statusText}`;
} else if (error.code === 'ECONNABORTED') {
errorMsg = '请求超时';
} else {
errorMsg = '网络错误,请稍后重试';
}
return Promise.reject(new Error(errorMsg));
}
);
// 使用时只需简单 try/catch
async function useAxios() {
try {
const data = await axios.get('/api/data');
console.log('成功:', data);
} catch (error) {
console.error('请求错误:', error.message);
}
}
四、通用错误处理原则
-
分层捕获:
- 网络层:捕获断网、超时、跨域等问题;
- HTTP 层:处理 4xx/5xx 状态码;
- 业务层:判断接口返回的
code等业务字段。
-
用户体验:错误发生时,给用户清晰提示(如 “网络不佳,请重试”“服务器开小差了”),避免技术术语。
-
错误上报:将错误信息(URL、状态码、错误信息、堆栈、用户设备等)上报到监控系统,便于排查问题:
javascript
运行
function reportError(info) { // 用 beacon API 确保页面关闭时也能上报 navigator.sendBeacon('/api/error-report', JSON.stringify({ ...info, pageUrl: location.href, userAgent: navigator.userAgent, time: new Date().toISOString() })); } -
重试机制:对非致命错误(如偶发网络波动),可实现自动重试(限制重试次数):
javascript
运行
async function requestWithRetry(url, retries = 3) { try { return await fetchData(url); } catch (error) { if (retries > 0 && error.message.includes('网络错误')) { console.log(`重试第 ${4 - retries} 次...`); return requestWithRetry(url, retries - 1); } throw error; // 重试耗尽后抛出错误 } }
通过以上方式,可全面覆盖网络请求中的各类错误,既保证用户体验,又便于问题排查。实际开发中,推荐使用 axios 等成熟库,减少重复开发成本。
3、性能指标如何监测计算?
前端性能指标监测是优化用户体验的核心环节,主流指标可分为核心 Web 指标(Core Web Vitals) 和传统性能指标,监测方式包括浏览器 API、第三方工具等。以下是关键指标的计算逻辑和监测方法:
一、核心 Web 指标(Core Web Vitals)
由 Google 提出,反映用户体验的三个关键维度:加载性能、交互响应性、视觉稳定性。
1. LCP( Largest Contentful Paint,最大内容绘制)
-
定义:衡量页面加载性能,指视口内最大的内容元素(文本块、图片等)渲染完成的时间。
-
计算逻辑:
- 监测页面中所有元素的渲染时间,筛选出 “最大内容元素”(面积最大,或文本块字符数最多)。
- 记录该元素从页面开始加载到渲染完成的时间(精确到毫秒)。
-
监测方法:
javascript
运行
// 使用 PerformanceObserver 监听 LCP new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { console.log('LCP 时间:', entry.startTime); // 单位:ms // entry 包含元素信息(element)、尺寸(size)等 } }).observe({ type: 'largest-contentful-paint', buffered: true }); -
达标阈值:≤ 2.5 秒(良好),> 4 秒(较差)。
2. FID(First Input Delay,首次输入延迟)
(注:FID 已被INP(Interaction to Next Paint) 替代,2024 年 3 月起成为核心指标)
-
INP 定义:衡量交互响应性,计算用户所有交互(点击、输入等)从触发到浏览器绘制下一帧的延迟,取其中最长的 98% 分位值。
-
计算逻辑:
- 记录每次用户交互(click、keydown 等)的开始时间(
processingStart)。 - 记录浏览器处理完交互并开始绘制下一帧的时间(
processingEnd)。 - 单次延迟 =
processingEnd - processingStart,最终取所有延迟的 98% 分位值。
- 记录每次用户交互(click、keydown 等)的开始时间(
-
监测方法:
javascript
运行
new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { console.log('交互延迟:', entry.duration); // 单位:ms } }).observe({ type: 'event-timing', buffered: true }); -
达标阈值:≤ 200ms(良好),> 500ms(较差)。
3. CLS(Cumulative Layout Shift,累积布局偏移)
-
定义:衡量视觉稳定性,计算页面生命周期内所有意外布局偏移的总和。
-
计算逻辑:
- 每次布局变化时,计算 “不稳定元素”(位置 / 尺寸变化的元素)的影响面积(
impact fraction)和移动距离(distance fraction)。 - 单次偏移分数 = 影响面积 × 移动距离。
- CLS = 所有单次偏移分数的总和。
- 每次布局变化时,计算 “不稳定元素”(位置 / 尺寸变化的元素)的影响面积(
-
监测方法:
javascript
运行
new PerformanceObserver((entryList) => { let cls = 0; for (const entry of entryList.getEntries()) { if (!entry.hadRecentInput) { // 排除用户交互导致的偏移(如点击展开) cls += entry.value; } } console.log('CLS 分数:', cls); }).observe({ type: 'layout-shift', buffered: true }); -
达标阈值:≤ 0.1(良好),> 0.25(较差)。
二、传统性能指标
补充核心指标未覆盖的加载和渲染阶段。
function getNavigationTiming() {
const timing = performance.timing;
const now = Date.now();
return {
// 关键时间点
redirect: timing.redirectEnd - timing.redirectStart,
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ssl: timing.secureConnectionStart ? timing.connectEnd - timing.secureConnectionStart : 0,
ttfb: timing.responseStart - timing.requestStart, // Time to First Byte
response: timing.responseEnd - timing.responseStart,
dom: timing.domContentLoadedEventEnd - timing.domLoading,
domInteractive: timing.domInteractive - timing.navigationStart,
domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
load: timing.loadEventEnd - timing.navigationStart,
// 核心指标
firstPaint: timing.responseEnd - timing.navigationStart,
firstContentfulPaint: getFirstContentfulPaint(),
// 计算白屏时间
whiteScreen: timing.responseEnd - timing.navigationStart
};
}
// 页面加载完成后获取
window.addEventListener('load', () => {
setTimeout(() => {
const metrics = getNavigationTiming();
console.log('Navigation Timing:', metrics);
reportPerformance(metrics);
}, 0);
});
浏览器性能指标监测计算以浏览器原生 API 为核心数据来源,重点围绕页面加载、交互、渲染三大环节,按 “采集原始数据→计算标准指标→分析优化” 的流程执行,核心是利用Performance系列 API 获取数据并结合 Web 标准公式计算。
一、明确浏览器核心性能指标分类
浏览器性能指标可分为加载类、交互类、渲染类三大类,覆盖用户从打开页面到操作页面的全流程体验。
-
加载类指标:反映页面内容呈现的速度。
- TTFB(首字节时间):从发起请求到接收服务器第一个字节的时间,体现网络与服务器响应速度。
- FCP(首次内容绘制):页面首次出现文本、图像等非空白内容的时间,是加载的早期信号。
- LCP(最大内容绘制):页面最大文本块或图像完成渲染的时间,是加载体验的核心指标。
-
交互类指标:反映页面操作的流畅度。
- FID(首次输入延迟):用户首次交互(如点击按钮)到浏览器开始处理的时间,体现交互响应速度。
- TTI(可交互时间):页面完全加载并能稳定响应用户交互的时间,需无长任务阻塞。
-
渲染类指标:反映页面显示的稳定性。
- CLS(累积布局偏移):页面元素意外偏移的累积分数,体现页面视觉稳定性。
- FPS(帧率):每秒页面重绘次数,正常需≥60FPS,低于则会出现卡顿。
二、关键监测计算步骤
1. 数据采集:依赖浏览器原生 API
所有指标的原始数据均通过浏览器内置的Performance系列 API 获取,无需额外插件,核心 API 包括Performance、PerformanceObserver。
-
基础数据采集:用
performance.getEntriesByType()获取特定类型的性能数据,如导航、资源加载数据。-
示例(获取加载类基础数据):
javascript
运行
// 获取导航相关数据(包含TTFB、FCP等基础信息) const navEntry = performance.getEntriesByType('navigation')[0]; // 获取资源加载数据(如图片、JS文件的加载时间) const resourceEntries = performance.getEntriesByType('resource');
-
-
实时指标监听:用
PerformanceObserver监听动态产生的指标(如 LCP、CLS),避免错过异步加载的内容。-
示例(监听 LCP 指标):
javascript
运行
new PerformanceObserver((entryList) => { const lcpEntry = entryList.getEntries().pop(); // 取最后一个(最大内容元素) console.log('LCP时间:', lcpEntry.startTime); // 单位:毫秒 }).observe({ type: 'largest-contentful-paint', buffered: true });
-
2. 指标计算:按 Web 标准公式转化
不同指标的计算逻辑由 W3C 或 Web Vitals 标准定义,直接基于 API 返回数据推导,无需自定义复杂逻辑。
- TTFB:直接取
navEntry.responseStart的值,该字段已包含 “请求发起→服务器返回首字节” 的总时间。 - FCP:通过
PerformanceObserver监听first-contentful-paint事件,取entry.startTime。 - LCP:同上,监听
largest-contentful-paint事件,取最后一个 entry 的startTime。 - FID:监听
first-input事件,计算entry.processingStart - entry.startTime(输入触发到开始处理的差值)。 - CLS:对每次布局偏移,计算 “影响分数(元素偏移区域占视口比例)× 距离分数(元素偏移距离占视口比例)”,再累加所有分数。
- FPS:通过
requestAnimationFrame记录时间差,计算1000 / (当前时间 - 上一帧时间),得到每秒帧率。
3. 分析与验证:工具辅助判断
采集计算后,需通过工具验证指标是否达标,并定位性能瓶颈。
- 实时调试:用 Chrome DevTools 的
Performance面板录制页面加载 / 交互过程,直观查看各指标耗时及长任务、资源加载阻塞问题。 - 批量分析:用
Lighthouse(Chrome 插件或 CLI 工具)批量检测页面,生成包含所有指标的评分报告,明确优化方向。 - 用户真实数据:通过
Chrome User Experience Report(CrUX)获取真实用户的浏览器性能数据,避免仅依赖实验室数据。
4、同时上传1000个视频如何做?
前端同时上传 1000 个视频需通过 “分片上传 + 并发控制 + 性能优化” 组合方案实现,核心是避免单线程阻塞和带宽过载,同时保障上传稳定性与用户体验。
一、核心技术方案:解决大数量、大文件上传痛点
1. 分片上传:拆分大文件,降低单次传输压力
将每个视频文件拆分为固定大小的分片(如 5MB / 片),通过多请求并行传输,避免单个大文件上传超时或失败。
-
实现逻辑:
- 使用
File.slice()方法将视频文件分割为多个Blob对象。 - 为每个分片生成唯一标识(如基于文件 MD5 + 分片索引),确保服务端能正确合并。
- 分片上传完成后,向服务端发送 “合并请求”,由服务端拼接所有分片为完整文件。
- 使用
-
优势:支持断点续传(未上传的分片可重新上传),减少重复传输成本。
2. 并发控制:限制同时上传的请求数,避免浏览器崩溃
浏览器对同一域名的并发请求数有限制(通常 6 个),直接上传 1000 个文件会导致请求排队阻塞,需通过 “队列 + 并发池” 控制请求数量。
-
实现逻辑:
- 将所有视频的分片按顺序加入上传队列。
- 维护一个固定大小的并发池(如同时处理 3-5 个请求),池内有空闲位置时,从队列中取出分片继续上传。
- 监听每个请求的成功 / 失败回调,失败时自动重试(设置最大重试次数,如 3 次),成功则继续从队列取任务。
-
示例代码(简化版并发控制) :
javascript
运行
class UploadQueue { constructor(concurrency = 3) { this.concurrency = concurrency; // 最大并发数 this.queue = []; // 待上传任务队列 this.running = 0; // 正在运行的任务数 } // 添加任务到队列 addTask(task) { this.queue.push(task); this.run(); // 尝试执行任务 } // 执行队列任务 run() { if (this.running >= this.concurrency || this.queue.length === 0) return; this.running++; // 从队列头部取出任务执行 const task = this.queue.shift(); task() .then(() => { /* 任务成功回调 */ }) .catch(() => { /* 任务失败重试逻辑 */ }) .finally(() => { this.running--; this.run(); // 继续执行下一个任务 }); } }
二、前端性能优化:减少上传对页面的影响
1. 避免主线程阻塞:用 Web Worker 处理复杂逻辑
文件分片、MD5 计算(用于断点续传)等操作会占用主线程,导致页面卡顿,需用Web Worker在后台线程处理。
-
优化点:
- 在
Web Worker中完成文件分片、生成分片 MD5 值,计算完成后通过postMessage将结果传给主线程。 - 主线程仅负责发起上传请求和更新 UI(如进度条),不处理耗时计算。
- 在
2. 进度与内存优化:精准展示进度,避免内存泄漏
-
进度展示:
- 按 “文件级进度” 和 “整体进度” 分别统计:单个文件进度 = 已上传分片数 / 总分片数;整体进度 = 已上传文件数 / 总文件数。
- 使用
XMLHttpRequest.upload.onprogress监听每个分片的上传进度,实时更新 UI,避免频繁 DOM 操作(可通过requestAnimationFrame批量更新)。
-
内存优化:
- 上传完成的分片
Blob对象及时释放,避免累计占用内存。 - 对长时间未上传的任务(如网络中断),自动取消请求并清空相关资源。
- 上传完成的分片
3. 网络适配:根据网络状况动态调整策略
通过navigator.connection.effectiveType判断用户网络类型(如 4G/5G/WiFi),动态调整并发数和分片大小。
- 网络较好(WiFi/5G):并发数设为 5-6,分片大小设为 10MB,提升上传速度。
- 网络较差(4G/3G):并发数设为 1-2,分片大小设为 2-5MB,减少请求失败概率。
三、配套保障措施:确保上传稳定与可恢复
1. 断点续传:避免网络中断后重新上传
- 实现逻辑:上传前先向服务端请求 “已上传的分片列表”,仅上传未完成的分片,减少重复传输。
- 依赖条件:需为每个文件生成唯一标识(如通过
spark-md5库计算文件 MD5),服务端根据该标识记录分片上传状态。
2. 错误处理:重试与降级机制
- 请求重试:对因网络波动导致的失败(如 HTTP 5xx 错误),设置 3 次自动重试,每次重试前等待 1-3 秒(指数退避策略)。
- 降级处理:若浏览器不支持
File.slice()或Web Worker(极少数旧浏览器),自动降级为单文件上传,提示用户 “建议使用现代浏览器以提升体验”。
3. 服务端配合:确保前端方案可落地
前端方案需服务端支持以下能力:
- 接收分片文件,并按 “文件唯一标识 + 分片索引” 存储。
- 提供 “查询已上传分片” 接口,支持断点续传。
- 提供 “合并分片” 接口,将同一文件的所有分片拼接为完整文件。 确保前端同时上传1000个视频的性能,关键在于 “化同步为异步,化并发为队列” 。
- 核心:实现一个带并发控制的上传队列。
- 基石:使用分片上传和客户端直传OSS的技术组合。
- 体验:提供详尽的进度反馈和用户控制能力(暂停/继续/取消)。
- 健壮性:考虑错误重试和断点续传。