恩士迅

28 阅读16分钟

1、css会阻塞html的渲染吗

在 CSS 与 HTML 的渲染关系中,CSS 会阻塞 HTML 的渲染,但不会阻塞 HTML 的解析,具体逻辑如下:

  1. CSS 阻塞渲染的原因浏览器在渲染页面时,需要先构建 CSSOM(CSS 对象模型)  和 DOM(文档对象模型) ,然后将两者结合生成 渲染树(Render Tree) ,才能进行页面渲染。如果 CSS 尚未加载或解析完成,CSSOM 不完整,浏览器无法确定元素的最终样式(例如尺寸、颜色、位置等),因此会暂停渲染过程,等待 CSSOM 构建完成后再继续。这意味着,若外部 CSS 文件加载缓慢,会导致页面长时间处于空白状态(解析仍在进行,但渲染被阻塞)。
  2. CSS 不阻塞 HTML 解析HTML 解析(构建 DOM)和 CSS 解析(构建 CSSOM)是 并行进行 的。浏览器在下载 CSS 的同时,会继续解析 HTML 生成 DOM 树,不会因为 CSS 未加载完成而停止解析 HTML。这种并行处理机制是为了提高效率,避免资源加载阻塞文档结构的解析。
  3. 特殊情况: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);
  });

// 方式2async/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}`;
}

关键注意事项

  1. 避免错误吞噬:捕获错误后必须处理(如上报、提示),不要空 catch
  2. 区分错误类型:针对不同错误(JS 执行、资源加载、接口)做差异化处理。
  3. 堆栈信息完整:错误对象的 stack 属性包含调用栈,是排查问题的关键(注意生产环境可能被压缩,需配合 sourcemap 还原)。
  4. 跨域脚本错误:若页面引入跨域脚本,window.onerror 可能无法获取详细错误信息,需在脚本标签添加 crossorigin 属性,并确保服务端返回正确的 CORS 头。

3、网络请求错误如何捕获

网络请求错误的捕获需要覆盖网络层面异常(如断网、超时)和业务层面异常(如 HTTP 4xx/5xx 状态码),不同请求方式(XMLHttpRequestfetchaxios 等)的处理方式略有差异,以下是具体实现:

一、原生 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);
  }
}

四、通用错误处理原则

  1. 分层捕获

    • 网络层:捕获断网、超时、跨域等问题;
    • HTTP 层:处理 4xx/5xx 状态码;
    • 业务层:判断接口返回的 code 等业务字段。
  2. 用户体验:错误发生时,给用户清晰提示(如 “网络不佳,请重试”“服务器开小差了”),避免技术术语。

  3. 错误上报:将错误信息(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()
      }));
    }
    
  4. 重试机制:对非致命错误(如偶发网络波动),可实现自动重试(限制重试次数):

    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% 分位值。
  • 监测方法

    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 包括PerformancePerformanceObserver

  • 基础数据采集:用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 / 片),通过多请求并行传输,避免单个大文件上传超时或失败。

  • 实现逻辑

    1. 使用File.slice()方法将视频文件分割为多个Blob对象。
    2. 为每个分片生成唯一标识(如基于文件 MD5 + 分片索引),确保服务端能正确合并。
    3. 分片上传完成后,向服务端发送 “合并请求”,由服务端拼接所有分片为完整文件。
  • 优势:支持断点续传(未上传的分片可重新上传),减少重复传输成本。

2. 并发控制:限制同时上传的请求数,避免浏览器崩溃

浏览器对同一域名的并发请求数有限制(通常 6 个),直接上传 1000 个文件会导致请求排队阻塞,需通过 “队列 + 并发池” 控制请求数量。

  • 实现逻辑

    1. 将所有视频的分片按顺序加入上传队列。
    2. 维护一个固定大小的并发池(如同时处理 3-5 个请求),池内有空闲位置时,从队列中取出分片继续上传。
    3. 监听每个请求的成功 / 失败回调,失败时自动重试(设置最大重试次数,如 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个视频的性能,关键在于  “化同步为异步,化并发为队列”
  1. 核心:实现一个带并发控制的上传队列
  2. 基石:使用分片上传客户端直传OSS的技术组合。
  3. 体验:提供详尽的进度反馈用户控制能力(暂停/继续/取消)。
  4. 健壮性:考虑错误重试断点续传