前端面试技术性场景题

87.场景面试之大数运算:超过js中number最大值的数怎么处理

在 JavaScript 中,Number.MAX_SAFE_INTEGER(即 2^53 - 1,即 9007199254740991)是能被安全表示的最大整数。超过此值时,普通的 Number 类型会出现精度丢失问题,导致运算结果不准确。


如何处理大数运算?

JavaScript 提供了几种处理大数的方法:

1. 使用 BigInt(ES11 引入,适用于整数运算)

BigInt 是 JavaScript 原生支持的大整数类型,适用于超过 Number.MAX_SAFE_INTEGER 的整数运算。

const bigNum1 = BigInt("9007199254740991000000");
const bigNum2 = BigInt("123456789123456789");

console.log(bigNum1 + bigNum2);  // 9007199378197780123456n
console.log(bigNum1 * bigNum2);  // 正确计算超大数相乘

注意

  • BigInt 不能与 Number 直接混合运算,必须转换。
  • Math 库不支持 BigInt,例如 Math.sqrt(BigInt(16)) 会报错。

2. 使用 big.jsbignumber.jsdecimal.js 等第三方库(适用于小数运算)

BigInt 只适用于整数,对于大浮点数运算,可以使用 bignumber.js 这样的库:

import BigNumber from "bignumber.js";

const num1 = new BigNumber("9007199254740991000000");
const num2 = new BigNumber("123456789.123456789");

console.log(num1.plus(num2).toString()); // 精确计算大数加法
console.log(num1.times(num2).toString()); // 精确计算大数乘法

优势

  • 适用于浮点数运算,避免 JavaScript 内置 Number 的精度问题。
  • 计算结果可以转换为字符串,避免 BigInt 只支持整数的问题。

3. 使用字符串手写大数运算(适用于特定场景)

如果第三方库不适用,也可以手写字符串处理大数:

function addBigNumbers(a, b) {
  let res = "";
  let carry = 0;
  let i = a.length - 1, j = b.length - 1;

  while (i >= 0 || j >= 0 || carry) {
    let sum = (i >= 0 ? +a[i] : 0) + (j >= 0 ? +b[j] : 0) + carry;
    carry = Math.floor(sum / 10);
    res = (sum % 10) + res;
    i--; j--;
  }

  return res;
}

console.log(addBigNumbers("9007199254740991", "123456789123456789")); 
// 输出: "132463988378197780"

适用场景

  • 仅涉及加法、乘法等基本运算。
  • 不想引入 BigInt 或外部库。

总结

方案适用场景优势限制
BigInt超大整数运算内置支持,性能好仅支持整数,无法处理小数
big.js / bignumber.js超大整数 & 浮点数运算兼容小数计算,API 强大需要引入库
字符串模拟计算自定义大数计算适用于前端特殊场景实现复杂,性能较低

面试回答时,最好结合具体业务场景推荐方案,比如:

  • 如果是整数运算,可以直接使用 BigInt
  • 如果涉及小数运算,建议使用 bignumber.js
  • 如果环境不允许引入第三方库,可使用字符串模拟计算。

这样既展现了基础知识,又体现了实践经验,能够给面试官留下不错的印象! 🚀

88.场景面试之大文件上传(上下)

回答怎么解决,造个开发遇到的问题然后如何解决


一、问题场景描述

假设在开发一个云盘系统时,用户需要上传 10GB 以上的大文件,但在实际测试中遇到以下问题:

  1. 上传中断:网络波动导致上传失败后需重新上传整个文件。
  2. 内存溢出:前端直接读取整个文件导致浏览器崩溃。
  3. 进度反馈缺失:用户无法感知上传进度。
  4. 服务器压力大:大文件直接上传占用带宽且易超时。

二、解决方案:分片上传 + 断点续传

1. 前端核心步骤

a. 文件分片(File Chunking)

  • 技术点:使用 Blob.slice() 将文件切割为固定大小(如 5MB)的分片。

  • 代码示例

    function createChunks(file, chunkSize = 5 * 1024 * 1024) {
      const chunks = [];
      let start = 0;
      while (start < file.size) {
        chunks.push(file.slice(start, start + chunkSize));
        start += chunkSize;
      }
      return chunks;
    }
    

b. 生成文件唯一标识(Hash)

  • 目的:用于断点续传时识别文件。

  • 优化:使用 Web Worker + SparkMD5 计算文件 Hash,避免主线程阻塞。

    // 在 Web Worker 中计算 Hash
    self.importScripts('spark-md5.min.js');
    self.onmessage = async (e) => {
      const { chunks } = e.data;
      const spark = new self.SparkMD5.ArrayBuffer();
      for (const chunk of chunks) {
        spark.append(await chunk.arrayBuffer());
      }
      self.postMessage(spark.end());
    };
    

c. 上传分片(并发控制)

  • 并发控制:限制同时上传的分片数(如 3 个并行)。

  • 断点续传:上传前向服务端查询已上传的分片列表。

    async function uploadChunks(chunks, fileHash) {
      const uploaded = await checkExistingChunks(fileHash); // 查询已上传分片
      const pool = new ConcurrentPool(3); // 自定义并发池
      chunks.forEach((chunk, index) => {
        if (!uploaded.includes(index)) {
          pool.addTask(() => uploadChunk(chunk, index, fileHash));
        }
      });
      await pool.run();
    }
    

d. 进度反馈

  • 监听每个分片进度:使用 Axios 的 onUploadProgress

  • 汇总总进度

    let uploadedSize = 0;
    const totalSize = file.size;
    chunks.forEach((chunk, index) => {
      uploadChunk(chunk, index).then(() => {
        uploadedSize += chunk.size;
        updateProgress(uploadedSize / totalSize * 100);
      });
    });
    

2. 服务端核心步骤

a. 接收分片

  • 接口设计POST /upload-chunk,接收分片内容、文件 Hash、分片索引。
  • 存储分片:将分片保存到临时目录(按文件 Hash 分类存储)。

b. 合并分片

  • 接口设计POST /merge-chunks,根据文件 Hash 合并所有分片。

  • 合并逻辑(Node.js 示例):

    function mergeChunks(fileHash, fileName) {
      const chunkDir = path.join(UPLOAD_DIR, fileHash);
      const chunks = fs.readdirSync(chunkDir).sort((a, b) => a - b);
      const filePath = path.join(UPLOAD_DIR, fileName);
      const writeStream = fs.createWriteStream(filePath);
      chunks.forEach(chunk => {
        const chunkPath = path.join(chunkDir, chunk);
        writeStream.write(fs.readFileSync(chunkPath));
        fs.unlinkSync(chunkPath); // 删除分片
      });
      writeStream.end();
    }
    

c. 支持断点续传

  • 记录已上传分片:使用 Redis 或数据库记录文件 Hash 对应的已上传分片索引。

三、解决开发中的典型问题

问题 1:分片上传后合并失败
  • 原因:分片顺序错乱或丢失。

  • 解决方案

    • 服务端按索引顺序合并分片。
    • 前端上传时确保分片索引连续。
问题 2:Hash 计算卡顿
  • 原因:大文件 Hash 计算阻塞主线程。
  • 解决方案:使用 Web Worker 异步计算。
问题 3:上传进度不准确
  • 原因:未考虑分片上传失败重试。
  • 解决方案:在进度计算中排除失败分片,重试成功后再累加。

四、优化点

  1. 动态分片大小:根据网络质量调整分片大小(弱网时减小分片)。
  2. 秒传功能:服务端校验文件 Hash 已存在时直接返回 URL。
  3. 压缩分片:前端对分片进行 Gzip 压缩(需权衡 CPU 占用)。
  4. 分片哈希校验:上传分片时附带分片 Hash,服务端校验完整性。

五、代码片段示例(前端并发控制)

class ConcurrentPool {
  constructor(concurrency) {
    this.concurrency = concurrency;
    this.queue = [];
    this.running = 0;
  }

  addTask(task) {
    this.queue.push(task);
  }

  async run() {
    while (this.queue.length > 0) {
      if (this.running < this.concurrency) {
        const task = this.queue.shift();
        this.running++;
        task().finally(() => {
          this.running--;
          this.run();
        });
      } else {
        await new Promise(resolve => setTimeout(resolve, 100));
      }
    }
  }
}

六、总结

通过分片上传、断点续传、并发控制、进度反馈等技术组合,可有效解决大文件上传的稳定性、性能和用户体验问题。核心在于前端合理拆分任务,服务端高效管理分片,同时结合业务需求优化传输策略。

89.场景面试之如果你是美团电影的,请问怎么实现一个电影票选座功能

简要总结:

  1. 核心功能

    • 可视化座位布局(Canvas/SVG + 动态数据渲染)。
    • 实时选座状态同步(WebSocket + 乐观锁冲突处理)。
    • 限制选座逻辑(最大数量、相邻推荐)。
  2. 技术实现

    • 前端:状态管理(Redux/Zustand)、交互优化(高亮/动画)。
    • 后端:座位库存管理(Redis锁+事务)、API设计(查询/锁定/合并)。
  3. 难点解决

    • 并发冲突:服务端原子化操作 + 前端乐观更新回滚。
    • 性能瓶颈:虚拟滚动/Canvas分块渲染。
    • 弱网兼容:本地缓存 + 状态同步重试。

一句话总结:通过实时通信、原子化状态管理和分层渲染优化,实现高并发下的流畅选座体验,核心解决数据一致性与性能问题。

89.场景面试之函数编程思想的理解

回答要点总结(函数式编程思想)

1. 什么是函数式编程?

函数式编程(FP)是一种以 “纯函数”、“不可变数据” 为核心的编程范式,强调 声明式编程,提高代码的可读性、可复用性和可测试性。


2. 核心概念
  • 纯函数(Pure Function) :相同输入必定返回相同输出,无副作用。
  • 不可变数据(Immutability) :不直接修改数据,而是返回新数据。
  • 高阶函数(Higher-Order Function) :函数可以接收函数作为参数或返回函数(如 mapfilter)。
  • 柯里化(Currying) :将多参数函数拆成多个单参数函数,提高复用性。
  • 函数组合(Composition) :把小函数组合成大函数,提高代码整洁度。

3. 对比面向对象编程(OOP)
函数式编程(FP)面向对象编程(OOP)
状态管理不修改状态,返回新状态修改对象内部状态
可测试性高,可预测低,状态依赖上下文
适用场景数据流转换(如 Redux)复杂对象建模(如 UI 组件)

4. React 中的应用
  • React 组件:函数组件 + Hooks 遵循 FP 思想(如 useState)。
  • Redux:Reducer 必须是纯函数,不能修改 state
  • 高阶组件(HOC) :类似高阶函数,提高组件复用性。

5. 典型面试回答示例

“函数式编程是一种编程范式,它强调纯函数和不可变数据,减少副作用,提高代码可维护性。在 React 中,函数式组件、Hooks、Redux 都体现了 FP 思想,例如 useState 遵循不可变性,Redux Reducer 也是纯函数。”

90.场景面试之前端水印功能的了解

简要总结:

  1. 实现方式

    • CSS 背景:通过重复平铺背景图或渐变生成水印,但易被删除。
    • Canvas/SVG:动态生成含文字/logo的水印图,转为Base64设为背景。
    • DOM 覆盖:用绝对定位的div覆盖全屏,设置pointer-events: none避免遮挡操作。
  2. 动态水印

    • 注入用户信息(如ID、手机号),通过前端JS生成个性化水印。
  3. 防删除

    • MutationObserver:监听水印DOM变化,被删时重新插入。
    • Shadow DOM:隐藏水印元素结构,增加删除难度。
  4. 注意事项

    • 性能优化(避免频繁重绘)。
    • 兼容打印场景(@media print 样式适配)。
    • 后端校验(防止前端水印被绕过,关键数据需后端叠加)。

核心难点:平衡防护强度与性能,纯前端无法绝对防篡改,需结合后端验证。

91.场景面试之设计一个全站请求耗时统计工具

全站请求耗时统计工具设计

1. 需求分析
  • 统计所有 HTTP 请求的耗时,包括 API 请求、静态资源加载等。
  • 计算平均请求耗时、最大耗时、最小耗时等关键指标。
  • 兼容 fetch、XMLHttpRequest(XHR)、Axios 等不同请求方式。
  • 可视化展示统计数据,或者上报到监控系统。

2. 方案设计
(1)劫持全局 fetchXMLHttpRequest

window 作用域下拦截 HTTP 请求,记录开始和结束时间,计算耗时。

(2)存储请求数据
  • 采用 数组 存储每次请求的耗时数据。
  • 计算 平均耗时、最大/最小耗时
  • 采用 定时器批量上报 避免性能开销。
(3)数据上报
  • 通过 定时器或阈值触发 发送统计数据到后端(可结合 localStorage 做数据缓存)。
  • 可视化:用 Web Console监控系统(如 Grafana、Sentry) 展示数据。

3. 代码实现
class RequestMonitor {
    constructor() {
        this.requests = [];
        this.hookFetch();
        this.hookXHR();
    }

    hookFetch() {
        const originalFetch = window.fetch;
        window.fetch = async (...args) => {
            const startTime = performance.now();
            const response = await originalFetch(...args);
            const endTime = performance.now();
            this.logRequest(args[0], endTime - startTime);
            return response;
        };
    }

    hookXHR() {
        const originalOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function (...args) {
            this._url = args[1]; // 记录请求 URL
            return originalOpen.apply(this, args);
        };

        const originalSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function (...args) {
            const startTime = performance.now();
            this.addEventListener("loadend", () => {
                const endTime = performance.now();
                requestMonitor.logRequest(this._url, endTime - startTime);
            });
            return originalSend.apply(this, args);
        };
    }

    logRequest(url, duration) {
        this.requests.push(duration);
        console.log(`[Request Monitor] ${url} 耗时: ${duration.toFixed(2)}ms`);
    }

    getStats() {
        if (this.requests.length === 0) return { avg: 0, max: 0, min: 0 };
        const total = this.requests.reduce((sum, time) => sum + time, 0);
        return {
            avg: (total / this.requests.length).toFixed(2),
            max: Math.max(...this.requests).toFixed(2),
            min: Math.min(...this.requests).toFixed(2),
        };
    }
}

// 启动请求监控
const requestMonitor = new RequestMonitor();

4. 统计 & 数据展示

调用 requestMonitor.getStats() 获取全站请求统计数据

console.log("全站请求耗时统计:", requestMonitor.getStats());

5. 扩展优化

支持 Axios

import axios from "axios";

axios.interceptors.request.use(config => {
    config.meta = { startTime: performance.now() };
    return config;
});

axios.interceptors.response.use(response => {
    const duration = performance.now() - response.config.meta.startTime;
    requestMonitor.logRequest(response.config.url, duration);
    return response;
});

上报后端

setInterval(() => {
    const stats = requestMonitor.getStats();
    navigator.sendBeacon("/api/log", JSON.stringify(stats));
}, 60000); // 每分钟上报

前端可视化 可用 Chart.js / ECharts 绘制请求耗时趋势图。


6. 总结

功能点实现方式
拦截请求fetch + XMLHttpRequest + Axios 拦截器
计算耗时performance.now() 计算请求耗时
数据存储数组存储请求时间,并计算平均/最大/最小耗时
数据上报setInterval + navigator.sendBeacon() 定时上报
可视化console.log + Chart.js 展示数据

92.场景面试之深度SEO优化

深度 SEO 优化方案(前端视角)

1. SEO 基础概念

SEO(Search Engine Optimization)是搜索引擎优化的简称,目标是 提高网站在搜索引擎中的排名,增加自然流量。主要分为:

  • 白帽 SEO(合规优化,如结构化数据、关键词优化)
  • 黑帽 SEO(作弊手段,如关键词堆砌,容易被搜索引擎惩罚)
  • 灰帽 SEO(介于两者之间,如购买外链)

2. 深度 SEO 优化方案(前端视角)

(1)HTML 结构优化

语义化 HTML

  • 使用 <header> <article> <section> <nav> 等语义化标签,让搜索引擎更容易理解网页内容。

Title & Meta 标签

<title>React 18 Suspense 新特性解析</title>
<meta name="description" content="本篇文章详细解析 React 18 的 Suspense 新特性,帮助开发者深入理解并优化应用性能。" />
<meta name="keywords" content="React, React18, Suspense, 前端, JavaScript" />
  • title:简短、包含主要关键词。
  • meta description:控制搜索引擎摘要,提高点击率(CTR)。
  • meta keywords:部分搜索引擎已不使用,但仍可提供参考。

H1-H6 结构

<h1>React 18 Suspense 新特性解析</h1>
<h2>1. 什么是 Suspense?</h2>
<h2>2. 如何使用 Suspense?</h2>
  • H1 只能有一个,确保核心主题明确。
  • H2-H6 作为层级结构,帮助搜索引擎理解文章内容。

图片优化

<img src="suspense-example.png" alt="React 18 Suspense 示例代码" />
  • 使用 alt 属性,描述图片内容,提升无障碍体验 & SEO。
  • 使用 loading="lazy" ,实现懒加载优化性能。

结构化数据(Schema.org)

  • 增强搜索结果,支持 富文本摘要
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "React 18 Suspense 新特性解析",
  "author": { "@type": "Person", "name": "张三" },
  "datePublished": "2025-03-04",
  "publisher": { "@type": "Organization", "name": "前端社区" }
}
</script>

(2)前端技术优化

SSR(服务器端渲染)

  • React + Next.js 或 Nuxt.js(Vue),让搜索引擎爬虫直接获取完整 HTML 内容,提高抓取效率。

静态站点生成(SSG)

  • 适用于内容不经常变动的网站,例如博客、文档站点(Gatsby.js、Next.js)。

懒加载与代码拆分

  • 避免首屏加载过大,提高首屏加载速度(搜索引擎更喜欢快的网站)。
const LazyComponent = React.lazy(() => import('./LazyComponent'));

(3)性能优化

页面加载速度

  • 使用 Lighthouse 分析 Core Web Vitals(核心网页指标)。
  • 减少阻塞渲染的 JS/CSS
<link rel="preload" href="style.css" as="style">
  • 启用 Gzip / Brotli 压缩 及 HTTP/2。
  • 使用 CDN(如 Cloudflare)优化静态资源加载。

图片优化

  • WebP 格式 替代 PNG/JPG,提升加载速度。
  • 使用 srcset 提供不同分辨率图片
<img src="image.jpg" srcset="image-2x.jpg 2x, image-3x.jpg 3x" />

减少不必要的重定向

  • 避免 多个 301/302 跳转,影响 SEO 排名。

(4)移动端优化

响应式设计

  • 使用 meta viewport,适配移动端。
<meta name="viewport" content="width=device-width, initial-scale=1">

PWA(渐进式 Web 应用)

  • 提供离线缓存、首页安装功能,提高用户体验。

(5)外链与内部链接优化

合理的 URL 结构

  • URL 短而清晰,如:
❌ /article?id=12345
✅ /react-18-suspense

内链优化

  • 文章内部使用锚点链接,提升爬取效率。
<a href="/react-18-suspense">深入了解 React 18 Suspense</a>

外链策略

  • 提供权威性高的外链(如 MDN、Google 开发者文档)。
  • 处理 死链 404(使用 robots.txt301 重定向)。

(6)SEO 监控与优化

Sitemap.xml

<url>
  <loc>https://www.example.com/react-18-suspense</loc>
  <lastmod>2025-03-04</lastmod>
  <priority>0.8</priority>
</url>
  • 让搜索引擎更快索引你的站点。

robots.txt

User-agent: *
Disallow: /admin/
Allow: /
  • 阻止搜索引擎爬取无关页面(如后台管理系统)。

Google Search Console & 百度搜索资源平台

  • 提交 Sitemap.xml 让爬虫更快发现新内容。
  • 监测 抓取错误 & 索引情况

Lighthouse 测试

npx lighthouse https://www.example.com --view
  • 通过 Chrome DevTools 进行性能 & SEO 评分分析。

7. 总结

优化点实现方式
HTML 结构语义化标签、Title、Meta、H1-H6、Alt 图片
技术选型SSR(Next.js)、SSG(Gatsby)、静态优化
性能优化懒加载、CDN、WebP、代码拆分
移动端适配响应式设计、PWA
外链 & 内链清晰 URL、站内站外链接优化
SEO 监控Sitemap、robots.txt、Google Search Console

这样回答既 系统全面,又 有前端落地方案,面试官肯定会满意!🔥

93.场景面试之图片性能优化的方案

前端图片性能优化方案

1. 选择合适的图片格式
  • WebP/AVIF:现代格式,压缩率高,支持透明和动画。使用 <picture> 标签提供回退:

    <picture>
      <source srcset="image.avif" type="image/avif">
      <source srcset="image.webp" type="image/webp">
      <img src="image.jpg" alt="示例">
    </picture>
    
  • SVG:矢量图形,适合图标和简单图形,缩放无损。

2. 图片压缩与优化
  • 工具压缩:使用工具(如 Squoosh、TinyPNG)降低文件大小。
  • 质量取舍:调整压缩率(如 JPEG 质量设为 60-80%)。
3. 响应式图片加载
  • srcsetsizes:按设备分辨率/视口宽度加载合适尺寸:

    <img src="small.jpg"
         srcset="medium.jpg 1000w, large.jpg 2000w"
         sizes="(max-width: 600px) 100vw, 50vw">
    
4. 懒加载技术
  • 原生属性loading="lazy"(兼容现代浏览器):

    <img src="placeholder.jpg" data-src="image.jpg" loading="lazy">
    
  • Intersection Observer API:动态加载可视区域图片:

    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          observer.unobserve(img);
        }
      });
    });
    document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
    
5. CDN 与缓存策略
  • CDN 加速:通过全球节点分发,减少延迟。
  • 缓存控制:设置 Cache-Control: max-age=31536000 并添加哈希文件名(如 image-abc123.jpg)实现长期缓存。
6. 减少 HTTP 请求
  • HTTP/2 多路复用:替代雪碧图,并行加载小文件。

  • 内联小图:Base64 编码(适用于 <1KB 图片):

    .icon {
      background: url(data:image/png;base64,...);
    }
    
7. 渐进式加载与占位符
  • 渐进式 JPEG:逐步渲染,提升感知速度。
  • LQIP (低质量占位符) :先加载模糊缩略图,再替换为高清图。
8. 替代方案与高级优化
  • CSS/SVG 替代效果:阴影、渐变、图标字体减少图片依赖。

  • 视频替代 GIF:使用 MP4 格式(体积减少 80%+):

    <video autoplay loop muted playsinline>
      <source src="animation.mp4" type="video/mp4">
    </video>
    
9. 预加载关键资源
  • <link rel="preload"> :提前加载首屏重要图片:

    <link rel="preload" href="hero-image.jpg" as="image">
    
10. 监控与自动化
  • 性能工具:Lighthouse、WebPageTest 分析优化点。
  • 自动化处理:构建工具(Webpack)集成 image-webpack-loader 自动压缩。
总结

综合应用格式选择、懒加载、CDN 和响应式设计,平衡质量与性能。优先优化首屏资源,持续监控调整策略。

94.场景面试之1000w行的表格你如何渲染,性能问题,那么多的dom节点怎么精简

前端渲染1000万行表格的优化方案


1. 核心问题分析
  • DOM 数量爆炸:1000万行直接渲染会导致内存占用过高(约 2GB+),页面崩溃。
  • 渲染性能差:DOM 操作阻塞主线程,滚动卡顿。
  • 交互延迟:事件监听器过多,响应缓慢。

2. 核心解决方案:虚拟滚动(Virtual Scrolling)

原理:仅渲染可视区域内的行,动态替换内容,保持 DOM 节点数恒定(如 50-100个)。
实现步骤

  1. 计算可视区域

    • 获取容器高度(如 600px)和行高(如 30px)→ 每屏显示 20 行。
  2. 监听滚动事件

    • 根据滚动位置计算起始索引(startIndex = Math.floor(scrollTop / rowHeight))。
  3. 动态渲染数据

    • 截取 data.slice(startIndex, startIndex + 20) 进行渲染。
  4. 占位元素撑开滚动条

    • 设置总高度为 totalHeight = rowHeight * 1e7,模拟完整滚动范围。

代码示例

const container = document.getElementById('table');
const rowHeight = 30;
let startIndex = 0;

function renderVisibleRows() {
  const scrollTop = container.scrollTop;
  startIndex = Math.floor(scrollTop / rowHeight);
  const endIndex = startIndex + Math.ceil(container.clientHeight / rowHeight);
  
  // 清空现有行
  container.innerHTML = '';
  
  // 添加可视行
  for (let i = startIndex; i <= endIndex; i++) {
    const row = document.createElement('div');
    row.style.height = `${rowHeight}px`;
    row.textContent = `Row ${i + 1}`;
    container.appendChild(row);
  }
  
  // 设置占位高度
  container.style.height = `${rowHeight * 1e7}px`;
}

container.addEventListener('scroll', renderVisibleRows);
renderVisibleRows(); // 初始渲染

3. 性能优化进阶

a. 减少 DOM 复杂度

  • 简化结构:用 <div> 替代 <table>,避免浏览器重排开销。
  • 复用 DOM 节点:池化已创建的 DOM 元素,减少创建/销毁开销。

b. 异步渲染分块

  • 使用 requestAnimationFramesetTimeout 分块更新,避免阻塞主线程:

    function chunkedRender() {
      let i = 0;
      function renderChunk() {
        for (let j = 0; j < 10; j++) {
          if (i >= data.length) return;
          // 渲染第 i 行
          i++;
        }
        requestAnimationFrame(renderChunk);
      }
      renderChunk();
    }
    

c. 高效数据存储

  • 二进制数据:使用 ArrayBufferTypedArray 存储数值型数据,减少内存占用。
  • 按需加载:通过 WebSocket 或分页 API 动态加载数据,避免一次性加载 1000 万条。

d. 禁用高耗能操作

  • 避免在行元素上绑定独立事件监听器,改用事件委托

    container.addEventListener('click', (e) => {
      const row = e.target.closest('.row');
      if (row) handleRowClick(row.dataset.id);
    });
    

4. 替代方案:Canvas 渲染

适用场景:纯展示型表格,交互需求少。
优势

  • 数万行数据流畅渲染(直接绘制,无 DOM 开销)。
  • 支持复杂视觉效果(渐变、动画)。

实现思路

  1. 绘制表头、表格线。
  2. 根据滚动位置计算渲染的数据范围。
  3. 使用 canvas.drawText() 绘制文本内容。

限制

  • 文本选择、点击交互需手动实现(通过坐标计算命中区域)。

5. 使用现成库加速开发
  • React 生态

    • react-window:支持虚拟滚动列表/表格。
    • react-virtualized:高级功能(动态行高、缓存)。
  • Vue 生态

    • vue-virtual-scroller:轻量级虚拟滚动。
  • 原生 JS 库

    • lit-virtualizer(Polymer 团队)。

6. 极端优化手段
  • Web Worker 预处理:将排序/过滤操作移至 Worker 线程。
  • GPU 加速:对固定部分使用 transform: translateZ(0) 触发硬件加速。
  • 增量渲染:优先渲染首屏,逐步加载剩余数据。

总结

方案适用场景优点缺点
虚拟滚动高交互需求(编辑、复杂样式)平衡性能与功能实现复杂度较高
Canvas 渲染纯展示、超大数据量(>100万行)极致性能,无 DOM 限制交互实现复杂
分页/懒加载非实时性需求简单易实现用户体验不连贯

核心原则

  • 按需渲染:绝不在同一时间操作超过 1000 个 DOM 节点。
  • 减少重排/重绘:使用绝对定位、CSS Transform 等优化渲染。
  • 内存管理:及时销毁不可见数据,避免内存泄漏。

95.场景面试之怎么实现前端页面截图

场景面试:如何实现前端页面截图

在面试中,回答如何实现前端页面截图,需要结合 不同的应用场景可行方案,并说明 技术选型的理由


1. 需求分析

前端页面截图通常涉及以下几种场景:

  1. 用户截取当前可见区域
  2. 用户截取整个网页(滚动截图)
  3. 截取指定 DOM 元素
  4. 下载截图并分享
  5. 截图后进行编辑(如裁剪、标注)

2. 方案选择

方法 1:使用 html2canvas(适用于可见区域 & DOM 截图)

适用场景

  • 截取 可见区域指定 DOM 元素
  • 适用于 现代浏览器
  • 纯前端实现,不依赖服务器

🔧 实现步骤

  1. 安装 html2canvas
npm install html2canvas
  1. 代码实现
import html2canvas from "html2canvas";

const captureScreenshot = async () => {
  const element = document.getElementById("captureArea"); // 选择截图区域
  const canvas = await html2canvas(element, {
    useCORS: true, // 解决跨域图片问题
    backgroundColor: null, // 透明背景
  });
  const imgURL = canvas.toDataURL("image/png"); // 转换为 base64 图片
  const link = document.createElement("a");
  link.href = imgURL;
  link.download = "screenshot.png"; // 下载截图
  link.click();
};

📝 优缺点

优势劣势
纯前端实现,无需后端支持不支持跨域图片(需要 useCORS: true
可截取指定 DOM 元素无法截取 iframe、视频等
适用于用户可见区域滚动截图支持不完善

方法 2:使用 dom-to-image(改进 html2canvas,支持更多特性)

适用场景

  • 需要更精准的 DOM 截图
  • 支持 SVG、字体嵌入
  • 适用于 动态内容

🔧 实现代码

import domtoimage from 'dom-to-image';

const captureDomImage = () => {
  const node = document.getElementById("captureArea");
  domtoimage.toPng(node)
    .then(dataUrl => {
      const link = document.createElement("a");
      link.href = dataUrl;
      link.download = "screenshot.png";
      link.click();
    })
    .catch(error => console.error("截图失败:", error));
};

📝 优缺点

优势劣势
html2canvas 兼容性更好仍然无法截图 iframe、视频
支持 SVG、Web 字体性能可能比 html2canvas 略慢

方法 3:使用 puppeteer(适用于整页截图,服务器端渲染)

适用场景

  • 后台批量生成网页截图
  • 支持滚动截图
  • 可截图 iframe 和跨域内容

🔧 实现步骤

  1. 安装 Puppeteer
npm install puppeteer
  1. Node.js 代码
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com", { waitUntil: "networkidle2" });

  // 截取整个页面
  await page.screenshot({ path: "screenshot.png", fullPage: true });

  await browser.close();
})();

📝 优缺点

优势劣势
可截取整页,支持滚动需要服务器支持(Node.js)
支持 iframe、视频、跨域不能直接在前端运行

方法 4:使用 canvas 结合 drawImage()(适用于视频或 iframe 截图)

适用场景

  • 需要截取 iframe视频
  • 适用于 动态内容截图

🔧 实现代码

const captureVideoFrame = (videoElement) => {
  const canvas = document.createElement("canvas");
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
  const ctx = canvas.getContext("2d");

  ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);

  const imgURL = canvas.toDataURL("image/png");
  return imgURL;
};

const video = document.querySelector("video");
const screenshot = captureVideoFrame(video);
console.log("截图 Base64:", screenshot);

📝 优缺点

优势劣势
可截取 videoiframe只能截取当前帧
适用于动态内容需要用户交互(如 play() 后再截图)

3. 选型对比

方案适用场景支持滚动截图支持 iframe依赖
html2canvas可见区域、DOM 截图纯前端
dom-to-image更精准的 DOM 截图纯前端
puppeteer整页截图、后台爬虫Node.js 服务器
canvas + drawImage()视频截图纯前端

4. 总结

最佳方案

  • 普通网页截图(可见区域)html2canvas / dom-to-image
  • 完整网页截图(包括滚动)puppeteer
  • 视频 / iframe 截图canvas.drawImage()

面试回答时,可以结合具体业务场景,提出最合适的解决方案,同时说明 优缺点技术选型依据,这样更显得专业!🔥

96.场景面试之移动端适配问题如何解决

场景面试:移动端适配问题如何解决?

在前端面试中,针对移动端适配问题,最佳的回答方式是 先分析适配的常见问题,再 提出不同的解决方案,最后 结合具体场景推荐最佳方案


1. 移动端适配的常见问题

  1. 设备屏幕尺寸多样化(不同设备宽高比、像素密度不同)
  2. 不同 DPR(设备像素比) (如 Retina 屏,1px 在高分屏上可能占多个物理像素)
  3. 不同系统、浏览器的默认样式(iOS/Android/Chrome/Safari/微信浏览器差异)
  4. 字体、图片模糊问题(高分屏需要更高清的图片资源)
  5. 触摸事件与鼠标事件的差异(点击延迟、滑动优化)

2. 适配方案

针对以上问题,常见的适配方案有以下几种:

方案 1:使用 viewport 进行屏幕缩放

<head> 中添加 meta viewport 控制视口:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

优点

  • 控制视口宽度,避免缩放影响布局
  • user-scalable=no 防止用户缩放页面

缺点

  • 不能解决不同设备 px 值不一样的问题

方案 2:CSS 响应式布局(媒体查询)

使用 @media 适配不同屏幕:

@media screen and (max-width: 768px) {
  body {
    font-size: 14px;
  }
}

@media screen and (max-width: 375px) {
  body {
    font-size: 12px;
  }
}

优点

  • 适用于各种尺寸设备
  • 代码易读,适用于 rem/em 配合 viewport

缺点

  • 需要手动编写多套样式,维护成本较高

方案 3:使用 rem + vw 进行动态适配

使用 rem(配合 html 动态 font-size
  1. 设置 html 根字体大小
function setRem() {
  const baseSize = 16;
  const scale = document.documentElement.clientWidth / 375; // 以 iPhone 6(375px)为基准
  document.documentElement.style.fontSize = `${baseSize * scale}px`;
}
setRem();
window.onresize = setRem; // 监听窗口变化
  1. 使用 rem 进行布局
.container {
  width: 10rem; /* 适配不同设备 */
}
使用 vw(CSS 直接适配)
.container {
  width: 50vw; /* 视口宽度的 50% */
}

优点

  • rem 适用于不同屏幕 px 变化
  • vw 适用于 全屏自适应布局

缺点

  • rem 需要动态计算 font-size
  • vw 在小屏幕下可能导致内容过大

方案 4:使用 flexgrid 进行自适应布局

使用 flex 可以适配不同屏幕:

.container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

优点

  • 适用于不同屏幕
  • 不需要手动指定 width,自适应效果好

缺点

  • 仅适用于 容器内部元素布局

方案 5:使用 图片文字DPR 适配

高清图片
@media screen and (-webkit-min-device-pixel-ratio: 2),
       screen and (min-resolution: 192dpi) {
  .logo {
    background-image: url("logo@2x.png");
  }
}
CSS image-set
.logo {
  background-image: image-set(
    "logo.png" 1x,
    "logo@2x.png" 2x,
    "logo@3x.png" 3x
  );
}

优点

  • 适配 Retina 屏幕,图片更清晰

缺点

  • 需要提供不同 DPR 版本的图片资源

3. 选型对比

方案适用场景适配能力代码复杂度维护成本
meta viewport适配初始缩放⭐⭐⭐
@media 媒体查询适配不同设备⭐⭐⭐⭐⭐⭐⭐
rem + vw全局适配⭐⭐⭐⭐⭐⭐⭐⭐⭐
flex/grid自适应布局⭐⭐⭐⭐⭐⭐
DPR 适配高分屏优化⭐⭐⭐⭐⭐⭐⭐

4. 总结

最佳适配方案

  • 小型项目(简单适配)viewport + @media
  • 中型项目(动态布局)rem + vw
  • 大型项目(高分屏 & 灵活适配)flex + vw + DPR 适配

97.场景题之web应用中如何对静态资源加载失败的场景做降级处理

场景题:Web 应用中如何对静态资源加载失败的场景做降级处理?

在 Web 应用中,静态资源(如 CSS、JS、图片、字体等)可能由于 CDN 故障、网络问题、文件丢失 等原因加载失败。为了保证用户体验,需要实现 降级处理,确保页面在关键资源缺失时仍能正常运行。


1. 常见静态资源加载失败的原因

  • CDN 资源不可用(服务器宕机、DNS 解析失败)
  • 网络问题(用户网络波动、弱网环境)
  • 浏览器拦截(CORS 限制、广告拦截插件)
  • 文件丢失或路径错误(错误的资源 URL)
  • 版本更新问题(缓存未更新导致 404)

2. 具体降级方案

针对不同类型的静态资源,采用不同的降级策略:

📌 (1) JavaScript 资源降级

问题:核心 JS 资源(如 Vue、React 框架)加载失败,可能导致页面无法交互。
解决方案

  1. 多 CDN 备选方案(动态切换)
<script src="https://cdn1.example.com/react.min.js" onerror="loadFallbackJS()"></script>
<script>
  function loadFallbackJS() {
    var script = document.createElement("script");
    script.src = "https://cdn2.example.com/react.min.js";
    document.head.appendChild(script);
  }
</script>
  1. 本地备份(CDN + 本地)
<script src="https://cdn.example.com/vue.min.js" onerror="this.onerror=null;this.src='/local/vue.min.js'"></script>

优点:减少单点依赖,保障 JS 可用
缺点:可能造成一定的延迟


📌 (2) CSS 资源降级

问题:CSS 失败可能导致页面错乱
解决方案

  1. 备用 CSS(多 CDN 或本地降级)
<link rel="stylesheet" href="https://cdn.example.com/style.css" onerror="this.onerror=null;this.href='/fallback/style.css';">
  1. 使用 <noscript> 兜底 如果 JS 和 CSS 都失效,可以提供最基本的 noscript 样式:
<noscript>
  <link rel="stylesheet" href="/fallback/basic.css">
</noscript>

优点:确保最基本的样式可用
缺点:无法应对所有 CSS 丢失的情况


📌 (3) 图片资源降级

问题:图片加载失败影响用户体验
解决方案

  1. 使用默认占位图
<img src="https://cdn.example.com/avatar.jpg" onerror="this.onerror=null;this.src='/fallback/default-avatar.jpg';">
  1. 使用 object-fit 确保布局稳定
img {
  width: 100px;
  height: 100px;
  object-fit: cover;
  background-color: #f0f0f0; /* 避免显示空白 */
}

优点:避免图片影响布局
缺点:占位图可能无法满足所有业务需求


📌 (4) 字体资源降级

问题:Web 字体加载失败会导致 FOUT(Flash of Unstyled Text)或页面显示异常
解决方案

  1. 使用 font-display: swap
@font-face {
  font-family: "CustomFont";
  src: url("/fonts/custom.woff2") format("woff2");
  font-display: swap;
}

优点:优先使用系统字体,避免页面空白
缺点:字体可能会有短暂的切换闪烁

  1. 提供多个字体备选
body {
  font-family: "CustomFont", "Arial", sans-serif;
}

优点:保证页面最起码的可读性


📌 (5) 整体降级策略

当多个资源加载失败时,可以通过 JS 监控 + 策略性降级 提供更优体验:

  1. 全局监听资源加载失败
window.addEventListener("error", function (event) {
  if (event.target.tagName === "SCRIPT") {
    console.error("JS 加载失败,尝试使用备用方案");
    loadFallbackJS();
  } else if (event.target.tagName === "LINK") {
    console.error("CSS 加载失败,加载降级样式");
    document.head.insertAdjacentHTML(
      "beforeend",
      '<link rel="stylesheet" href="/fallback/style.css">'
    );
  } else if (event.target.tagName === "IMG") {
    event.target.src = "/fallback/default-image.jpg";
  }
}, true);

优点:全局监控并自动降级
缺点:可能增加额外的降级逻辑


3. 监控与上报

为了防止资源加载失败影响用户体验,可以上报错误并分析:

window.addEventListener("error", function (event) {
  fetch("/log/error", {
    method: "POST",
    body: JSON.stringify({
      type: event.target.tagName,
      url: event.target.src || event.target.href,
      time: new Date().toISOString()
    })
  });
}, true);

优点:帮助分析资源加载失败原因
缺点:需要额外的日志存储服务


4. 总结

📌 资源降级策略对比

资源类型降级方案适用场景额外成本
JS 资源备用 CDN / 本地备份核心框架 & 业务 JS
CSS 资源备用 CDN / noscript 方案关键样式
图片资源默认占位图头像、Banner
字体资源font-display: swap / 备用字体自定义字体
监控 & 上报window.addEventListener("error")统计加载失败

📌 面试回答建议

  1. 先分析问题(静态资源加载失败的原因)
  2. 再提供不同资源类型的降级方案
  3. 最后补充监控 & 上报方案
  4. 结合实际场景选择最优方案

这样回答,既展现了技术广度(覆盖 JS、CSS、图片、字体等资源),又体现了深度(主动监控 + 降级方案 + 上报分析),

98.场景题之如何修改第三方npm包

场景题:如何修改第三方 NPM 包?

在实际开发中,有时候我们需要修改 第三方 NPM 包,可能是因为:

  1. Bug 修复:第三方库存在未修复的问题
  2. 功能扩展:需要新增或修改原有功能
  3. 兼容性问题:与当前项目或新版本的依赖冲突
  4. 个性化定制:需要更改样式、逻辑、API 等

下面介绍 不同场景下的修改方式,并分析 优缺点


方式 1:直接修改 node_modules 目录下的文件(不推荐)

✅ 适用场景

  • 临时调试或快速验证修改是否生效

❌ 缺点

  • npm installpnpm install 后会被覆盖
  • 团队协作难以维护

示例

cd node_modules/some-package
vim index.js  # 直接修改文件

🚨 不推荐,修改后不能持久化!


方式 2:使用 patch-package 生成补丁(推荐)

✅ 适用场景

  • 修改第三方包 但不想手动维护 fork 版本
  • 可持续管理修改,方便团队协作

步骤

  1. 安装 patch-package
npm install patch-package postinstall-postinstall --save-dev

postinstall-postinstall 确保补丁在 npm install 后自动应用

  1. 修改 node_modules 内的代码
vim node_modules/some-package/index.js  # 进行修改
  1. 生成补丁
npx patch-package some-package

生成 patches/some-package+1.0.0.patch 文件,记录改动

  1. package.json 添加 postinstall
"scripts": {
  "postinstall": "patch-package"
}
  1. 提交 patches 目录到 Git
git add patches
git commit -m "fix: modify some-package for compatibility"

📌 优点

  • 不会丢失npm install 后会自动应用补丁
  • 团队协作友好,代码改动可被 Git 追踪
  • 不影响原包的更新,可随时调整

🚀 推荐使用!


方式 3:Fork 该 NPM 包,维护自己的版本

✅ 适用场景

  • 长期维护修改较多 的情况下
  • 官方仓库不接受 PR 或修复周期太长

步骤

  1. Fork 该 NPM 包的 GitHub 仓库
  2. 在本地克隆
git clone https://github.com/your-username/some-package.git
cd some-package
  1. 修改代码
  2. 发布到 NPM(可选)
npm version patch  # 更新版本号
npm publish --access public  # 发布自己的 NPM 包
  1. 使用修改后的包
npm install your-username/some-package

📌 优点

  • 完全掌控代码,可随时更新
  • 适用于大规模修改

缺点

  • 需要自己维护更新,官方如果有新版本,需要手动同步

🚀 适合长期定制需求!


方式 4:在项目代码中重新封装

✅ 适用场景

  • 仅修改部分 API,但不想改动原包

思路

  • 通过继承或高阶函数封装
  • 代理某些方法,增强功能
  • 拦截包的导入,替换原实现

示例 1:封装增强功能

import OriginalLib from "some-package";

export default function CustomLib(...args) {
  const instance = new OriginalLib(...args);

  // 增强方法
  instance.newMethod = function () {
    console.log("This is a custom method!");
  };

  return instance;
}

示例 2:Webpack / Vite Alias 直接替换原包

"alias": {
  "some-package": "/src/custom-some-package.js"
}

🚀 适合小修改,减少维护成本!


总结

方式适用场景优点缺点
直接修改 node_modules快速调试立即生效不能持久化,安装后会丢失
patch-package ✅(推荐)修改少量代码易维护,自动应用仍依赖原包,适用小改动
Fork 仓库 & 维护长期维护/大改动完全掌控代码需要自己维护更新
封装重写修改 API / 兼容性低维护成本,不影响原包适用于部分 API 的调整

📌 最佳实践

  • 小改动用 patch-package
  • 大改动 Fork 维护
  • 不想改源码就封装 API

99.场景题之实现网页加载进度条

场景题:实现网页加载进度条

在 Web 开发中,加载进度条 可以提升用户体验,常见的场景包括:

  • 单页应用(SPA) 页面切换时的加载进度
  • 页面资源加载(CSS/JS/图片等)
  • 异步请求加载(Ajax 请求、接口调用)
  • 长时间任务(文件上传、数据处理)

方案 1:使用 NProgress 实现进度条(推荐)

NProgress 是一个轻量级的进度条库,能在 页面加载或 AJAX 请求时 显示进度条。

安装 NProgress

npm install nprogress

基本使用

import NProgress from "nprogress";
import "nprogress/nprogress.css"; // 引入样式

// 页面开始加载时
NProgress.start();

// 页面加载完成
NProgress.done();

fetch 结合

NProgress.start();
fetch("https://api.example.com/data")
  .then(response => response.json())
  .finally(() => NProgress.done());

📌 优点

  • 简单易用,适合异步请求、路由跳转
  • 可自定义样式
  • 自动处理多次请求

🚀 适合大多数场景,推荐!


方案 2:基于 XMLHttpRequest 监听网络请求进度

对于 文件上传大数据请求,可以使用 XMLHttpRequest 监听 progress 事件。

const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/large-file", true);

xhr.onprogress = function (event) {
  if (event.lengthComputable) {
    let percent = (event.loaded / event.total) * 100;
    console.log(`加载进度:${percent.toFixed(2)}%`);
  }
};

xhr.onload = function () {
  console.log("加载完成");
};

xhr.send();

📌 适用场景

  • 大文件下载
  • 流式加载

🚀 适用于特定场景,如文件上传


方案 3:基于 window.onload 监听静态资源加载

用于 网页所有资源(CSS/JS/图片)加载完毕 时显示进度条。

<div id="progress-bar" style="width: 0%; height: 5px; background: blue; position: fixed; top: 0; left: 0;"></div>
<script>
  let progressBar = document.getElementById("progress-bar");

  function updateProgress(percent) {
    progressBar.style.width = percent + "%";
  }

  document.addEventListener("DOMContentLoaded", () => updateProgress(50));
  window.onload = () => updateProgress(100);
</script>

📌 适用场景

  • 整页加载
  • 首屏优化

🚀 适合全局加载进度条


方案 4:监听路由变化(适用于 Vue / React)

对于 单页应用(SPA) ,可以监听路由变化,并在切换时显示进度条。

Vue 结合 NProgress

import NProgress from "nprogress";
import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
  history: createWebHistory(),
  routes: [ /* 路由配置 */ ]
});

router.beforeEach(() => NProgress.start());
router.afterEach(() => NProgress.done());

React 结合 NProgress

import { useEffect } from "react";
import NProgress from "nprogress";
import { useLocation } from "react-router-dom";

const ProgressBar = () => {
  const location = useLocation();

  useEffect(() => {
    NProgress.start();
    return () => NProgress.done();
  }, [location.pathname]);

  return null;
};

export default ProgressBar;

📌 适用场景

  • Vue / React 单页应用
  • 路由切换时显示进度

🚀 单页应用推荐方案


总结

方案适用场景优点缺点
NProgress ✅(推荐)Ajax、SPA 路由简单易用,UI 友好需引入库
XMLHttpRequest 进度监听文件上传、大数据进度精准仅适用于请求
window.onload 资源加载静态资源、首屏优化适用于整页不适用于 AJAX
Vue/React 路由进度条单页应用结合 NProgress,效果好需框架支持

最佳实践

  • AJAX / 路由加载NProgress
  • 文件上传XMLHttpRequest 监听 progress
  • 整页加载window.onload
  • Vue / React结合 beforeEach/useEffect

100.场景题之小程序双线程模型

小程序双线程模型解析


1. 核心架构

小程序采用**渲染层(View Layer)逻辑层(App Service Layer)**分离的双线程模型,通过 Native(客户端) 桥接通信,实现安全性与性能的平衡。


2. 线程职责
线程运行环境核心职责限制
渲染层WebView(iOS WKWebView / Android X5)- 页面渲染(WXML/WXSS)
- 用户交互事件监听
无法直接执行 JavaScript 业务逻辑
逻辑层JavaScriptCore(iOS) / V8(Android)- 业务逻辑处理(JS)
- 数据管理(setData)
- 调用原生 API
无法直接操作 DOM

3. 通信机制
  1. 事件驱动

    • 用户交互(如点击):渲染层捕获事件 → 通过 WeixinJSBridge 通知逻辑层。
    • 数据更新:逻辑层调用 setData → 数据序列化为字符串 → Native 转发 → 渲染层解析并更新视图。
    // 逻辑层(业务逻辑)
    Page({
      handleClick() {
        this.setData({ text: "Updated" }); // 数据变更通知渲染层
      }
    });
    
  2. 通信优化

    • 数据合并:多次 setData 调用合并为一次通信。
    • 局部更新:仅传递变化的数据字段,减少传输量。

4. 设计优势
  • 安全性

    • 逻辑层无法直接操作 DOM,防止恶意脚本攻击。
    • 敏感 API(如支付)由 Native 层控制,JS 需通过权限申请。
  • 性能

    • 渲染与逻辑分离,避免单线程阻塞(如长耗时逻辑不影响页面渲染)。
    • WebView 与 JS 引擎独立,内存管理更高效。
  • 稳定性

    • 单页面崩溃不影响整体应用(各页面渲染层独立)。

5. 性能瓶颈与优化
  • 通信开销

    • 问题:频繁 setData 或大数据量传输导致延迟。

    • 解决

      • 使用 this.data.xxx 直接修改数据,仅在必要时调用 setData
      • 分页加载数据,避免一次性传递超长列表。
  • 渲染层性能

    • 问题:复杂动画或过多节点导致卡顿。

    • 解决

      • 使用 CSS 动画替代 JS 动画。
      • 简化 WXML 结构,减少嵌套层级。

6. 与 Web 单线程模型的对比
对比项Web 单线程模型小程序双线程模型
线程模型渲染、逻辑、DOM 操作均在同一线程渲染与逻辑分离,Native 桥接通信
安全性可通过 JS 直接操作 DOM,风险较高逻辑层无法操作 DOM,安全性更强
性能瓶颈长任务阻塞渲染(如复杂计算)通信延迟(逻辑与渲染层数据传递)
开发限制无强制限制,灵活性高API 调用受限,需遵循小程序规范

7. 典型场景示例

用户输入实时搜索

  1. 渲染层监听输入事件 → 通知逻辑层。
  2. 逻辑层防抖处理 → 发起网络请求。
  3. 请求返回后通过 setData 更新列表 → 渲染层重新渲染。
    优化点
  • 防抖减少请求次数。
  • 仅更新差异数据,避免全列表刷新。

总结

双线程模型通过逻辑与渲染隔离保障了小程序的安全与流畅性,但开发者需注意:

  1. 控制 setData 的频率与数据量。
  2. 避免在渲染层执行复杂逻辑。
  3. 合理使用 Native 能力(如 Worker)处理耗时任务。
    这一设计是小程序高性能、高安全性的基石,但也对开发者的优化意识提出了更高要求。

101.场景题之如何使用一个链接实现PC打开是web应用,手机打开是h5应用

解决方案:通过设备检测实现自适应内容分发

1. 核心思路

使用 服务端 User-Agent 检测 区分设备类型,同一 URL 返回 PC 或 H5 的不同前端资源,实现“一链双端”。


2. 实现方案
方案一:服务端动态渲染(SSR)
  • 步骤

    1. 检测 User-Agent:服务端(如 Node.js、Nginx)解析请求头中的 User-Agent

    2. 返回对应内容

      • PC:返回 PC 版 HTML/CSS/JS。
      • 移动端:返回 H5 版 HTML/CSS/JS。
  • 代码示例(Node.js)

    const express = require('express');
    const mobileDetect = require('mobile-detect');
    const app = express();
    
    app.get('/', (req, res) => {
      const md = new mobileDetect(req.headers['user-agent']);
      if (md.mobile()) {
        res.sendFile('h5-index.html', { root: './h5' });
      } else {
        res.sendFile('pc-index.html', { root: './pc' });
      }
    });
    
  • 优点

    • URL 不变,无重定向延迟。
    • SEO 友好,可针对不同设备优化内容。
方案二:Nginx 路径分发
  • 配置示例

    map $http_user_agent $device {
      default "pc";
      ~*(android|iphone|ipod|ipad) "mobile";
    }
    
    server {
      location / {
        root /usr/share/nginx/html/$device;
        try_files $uri $uri/ /index.html;
      }
    }
    
  • 说明

    • 根据 UA 将请求映射到 pcmobile 目录,加载不同前端资源。
方案三:前端动态加载(CSR)
  • 步骤

    1. 前端通过 JS 检测设备类型。
    2. 动态加载对应版本的组件或路由。
    // 检测移动端
    const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
    
    // 动态加载组件
    if (isMobile) {
      import('./H5App').then(module => render(module.default));
    } else {
      import('./PCApp').then(module => render(module.default));
    }
    
  • 缺点

    • 首屏加载所有代码,影响性能。
    • 无法彻底隔离 PC/H5 的依赖包。

3. 关键优化点
  • 精准 UA 检测

    • 使用成熟库(如 mobile-detectreact-device-detect)提高识别准确率。
  • 缓存策略

    • 设置 Vary: User-Agent 响应头,避免 CDN 缓存混淆不同设备内容。
  • 降级方案

    • 当检测失败时,默认返回 PC 版并提供跳转链接(如“切换到移动版”)。
  • SEO 处理

    • 确保 PC/H5 页面核心内容一致,避免被判定为“内容重复”。

4. 方案对比
方案适用场景优点缺点
服务端动态渲染需严格区分功能/设计的场景性能高、SEO 友好需维护两套前端代码
Nginx 分发静态资源托管场景配置简单、无业务逻辑侵入灵活性较低
前端动态加载功能差异较小的轻量级应用单代码库维护首屏性能差、包体积大

5. 高级场景:响应式 + 动态增强
  • 混合方案

    1. 基础 UI 使用响应式布局适配所有设备。
    2. 针对复杂功能(如大屏图表),通过 UA 检测动态加载 PC 专属模块。
    // 响应式布局 + 按需加载
    if (!isMobile && isDataVisualizationPage) {
      import('heavy-charts-module').then(initCharts);
    }
    

总结

  • 推荐方案:服务端动态渲染(SSR)或 Nginx 分发,优先保证性能与体验一致性。

  • 核心原则

    • 设备检测精准化:避免误判导致功能错乱。
    • 代码维护低成本:通过组件复用减少双端开发量。
    • 用户体验无缝衔接:确保 URL 一致,功能切换自然。

102.场景题之移动端上拉加载,下拉刷新实现方案

场景题:移动端上拉加载 & 下拉刷新

在移动端开发中,上拉加载(加载更多数据)和 下拉刷新(重新获取最新数据)是常见的交互方式,常用于 列表、新闻流、商品展示等页面


一、核心思路

  • 下拉刷新(Pull to Refresh)

    • 用户下拉页面,触发 数据刷新
    • 适用于最新数据加载,如 新闻、社交动态
    • 依赖 touchstart touchmove touchend 事件
    • 也可以用 原生 WebView 下拉刷新JS 框架支持
  • 上拉加载(Infinite Scroll)

    • 用户滑动到底部,自动加载更多数据
    • 适用于分页数据,如 商品列表、文章列表
    • 依赖 scroll 事件 或 IntersectionObserver

二、手写原生实现

1. 下拉刷新(手写实现)

let startY = 0;
let isRefreshing = false;

document.addEventListener("touchstart", (e) => {
  startY = e.touches[0].pageY;
});

document.addEventListener("touchmove", (e) => {
  if (isRefreshing) return;

  let moveY = e.touches[0].pageY - startY;
  
  if (moveY > 50) {  // 下拉阈值
    console.log("触发刷新...");
    isRefreshing = true;
    refreshData();
  }
});

function refreshData() {
  setTimeout(() => {
    console.log("数据刷新完成");
    isRefreshing = false;
  }, 1500);
}

📌 适用场景

  • 适合轻量级项目,可结合 CSS 增强效果
  • 手动实现可定制性高

🚀 优化方向

  • 添加动画效果(例如旋转 loading 图标)
  • 防止多次触发
  • 结合CSS3 transform 过渡

2. 上拉加载(监听滚动事件)

window.addEventListener("scroll", () => {
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  let clientHeight = document.documentElement.clientHeight;
  let scrollHeight = document.documentElement.scrollHeight;

  if (scrollTop + clientHeight >= scrollHeight - 10) {
    console.log("触发加载更多...");
    loadMoreData();
  }
});

function loadMoreData() {
  setTimeout(() => {
    console.log("加载完成");
  }, 1000);
}

📌 适用场景

  • 适合PC + 移动端
  • 兼容性较好,但 scroll 事件可能会触发频繁

🚀 优化方向

  • 防抖优化(减少滚动触发频率)
  • 监听 scrollHeight 变化,防止无限触发

3. 上拉加载(IntersectionObserver 方案)

现代浏览器推荐使用 IntersectionObserver 监听目标元素是否进入视口

let observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    console.log("触发加载更多...");
    loadMoreData();
  }
});

observer.observe(document.querySelector("#load-more"));

📌 适用场景

  • 性能更优,比 scroll 监听更高效
  • 适用于 商品列表、无限滚动

三、使用前端框架(Vue/React)

1. Vue 实现

Vue 推荐使用 vueuse/useScrollbetter-scroll

<template>
  <div ref="list" @scroll="onScroll">
    <div v-for="item in listData" :key="item.id">{{ item.title }}</div>
    <div v-if="loading">加载中...</div>
  </div>
</template>

<script setup>
import { ref } from "vue";

const listData = ref([...Array(20).keys()]); // 模拟数据
const loading = ref(false);

const onScroll = (e) => {
  const { scrollTop, scrollHeight, clientHeight } = e.target;
  if (scrollTop + clientHeight >= scrollHeight - 10 && !loading.value) {
    loadMoreData();
  }
};

const loadMoreData = () => {
  loading.value = true;
  setTimeout(() => {
    listData.value.push(...Array(10).keys());
    loading.value = false;
  }, 1000);
};
</script>

📌 适用场景

  • 适用于 Vue 2 & Vue 3
  • 可结合 better-scroll 提供更流畅滚动

2. React 实现

React 也可以使用 useEffect + IntersectionObserver

import { useEffect, useState, useRef } from "react";

const InfiniteScroll = () => {
  const [items, setItems] = useState(Array.from({ length: 20 }));
  const [loading, setLoading] = useState(false);
  const loadMoreRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting && !loading) {
        setLoading(true);
        setTimeout(() => {
          setItems((prev) => [...prev, ...Array.from({ length: 10 })]);
          setLoading(false);
        }, 1000);
      }
    });

    if (loadMoreRef.current) observer.observe(loadMoreRef.current);
    return () => observer.disconnect();
  }, [loading]);

  return (
    <div>
      {items.map((_, index) => (
        <div key={index}>Item {index}</div>
      ))}
      <div ref={loadMoreRef}>{loading ? "加载中..." : "滑动加载更多"}</div>
    </div>
  );
};

export default InfiniteScroll;

📌 适用场景

  • React Hooks 风格
  • IntersectionObserver 减少性能开销

四、总结

方法适用场景优点缺点
原生 touch 事件适用于下拉刷新控制灵活,适配广需要手写逻辑
scroll 事件上拉加载适用 PC/移动端可能性能开销较大
IntersectionObserver ✅(推荐)上拉加载性能优越,简单高效兼容性略低(IE 需 polyfill)
Vue/React 方案SPA 项目结合框架特性需要组件化开发

最佳实践

  • 普通 Webscroll 事件 + 防抖
  • 现代 WebIntersectionObserver
  • Vue/React ➝ 结合 better-scrolluseEffect

这样回答,面试官会觉得你思路清晰、方案全面,绝对加分🔥!

103.区分并发和并行

并发(Concurrency)的概念

在计算机原理中,并发(Concurrency) 是指在同一时间段内处理多个任务,但这些任务不一定是同时执行的,而是通过任务切换来提高系统的吞吐量和资源利用率。


1. 并发 vs. 并行

  • 并发(Concurrency) :多个任务在同一时间段内交替执行(看起来像是同时运行的)。
  • 并行(Parallelism) :多个任务在同一时刻真正同时执行(需要多核 CPU)。

示例

  • 并发:单核 CPU 运行多个任务,任务 A 运行一会儿,切换到任务 B,再切换回来(任务交替执行)。
  • 并行:多核 CPU,任务 A 在核心 1 上运行,任务 B 在核心 2 上运行(真正的同时执行)。

2. 并发的实现方式

(1)多线程(Multi-threading)

  • 一个进程可以拥有多个线程,每个线程执行不同的任务。
  • 线程可以共享进程的内存,提高资源利用率。
  • 示例:浏览器渲染(主线程处理 UI 渲染,Worker 线程处理计算任务)。

(2)异步编程(Asynchronous Programming)

  • 适用于单线程环境(如 JavaScript)。
  • 通过事件循环(Event Loop)回调,让任务在等待 I/O 时不阻塞其他操作。
  • 示例:JavaScript 的 Promiseasync/await 机制

(3)协程(Coroutine)

  • 轻量级的线程,可以在执行过程中手动挂起和恢复
  • 比传统多线程开销更小,更适合高并发场景。
  • 示例:Python asyncio、Go 协程(Goroutine)

3. 并发的挑战

  • 竞争条件(Race Condition) :多个任务同时访问和修改共享资源,可能导致数据不一致。
  • 死锁(Deadlock) :多个任务相互等待对方释放资源,导致程序无法继续执行。
  • 资源争抢:任务切换会带来性能开销(如 CPU 线程调度)。

4. 并发的应用场景

  • Web 服务器(同时处理多个用户请求)
  • 数据库管理(多个事务并发执行)
  • 多任务操作系统(任务调度)
  • 前端异步操作(React 18 的并发模式)

5. 总结

  • 并发 是指多个任务在同一时间段内交替执行,提高 CPU 利用率。
  • 并行 是指多个任务在同一时刻同时执行,需要多核处理器支持。
  • 常见并发技术:多线程、异步编程、协程等。
  • 并发的挑战:竞争条件、死锁、资源争抢等问题需要妥善管理。

在前端开发(如 React 18)中,并发模式优化了渲染,使得 UI 更新更加流畅,也体现了计算机并发原理的实际应用。

104.为什么React中super()和super(props)不传props在子类构造函数中打印props是undefined,在生命周期中打印是正常访问

在 React 类组件中,构造函数内 this.propsundefined 的根本原因是:未将 props 传递给父类构造函数,导致父类 React.Component 未能正确初始化 this.props。以下是详细分析:


一、现象复现

class MyComponent extends React.Component {
  constructor(props) {
    super(); // 未传递 props
    console.log(this.props); // undefined
  }
}

二、底层原理

1. JavaScript 类继承机制
  • super() 的作用:调用父类构造函数,初始化父类属性和方法。
  • 参数传递:若子类构造函数中调用 super(props),则父类构造函数会接收到 props 参数;若调用 super(),则父类构造函数接收的 propsundefined
2. React 的 Component 类实现

React 的父类构造函数会接收 props 并初始化 this.props

class Component {
  constructor(props) {
    this.props = props; // 父类构造函数将 props 赋值给 this.props
    // ...其他初始化逻辑
  }
}
  • 若调用 super(props) :父类构造函数将 props 赋值给 this.props,子类构造函数中可访问。
  • 若调用 super() :父类构造函数中 propsundefined,因此 this.props 未被正确初始化。

三、为什么其他生命周期方法中 this.props 正常?

React 在组件实例化后(构造函数执行完毕后),会再次将 props 挂载到实例上,因此:

  • 构造函数外(如 rendercomponentDidMount):this.props 由 React 自动注入,与 super() 是否传递 props 无关。
  • 构造函数内this.props 的初始化依赖父类构造函数的执行,因此必须通过 super(props) 显式传递。

四、验证代码

class Parent {
  constructor(props) {
    this.props = props;
  }
}

class Child extends Parent {
  constructor(props) {
    super(); // 不传递 props
    console.log("构造函数内 this.props:", this.props); // undefined
  }

  method() {
    console.log("方法中 this.props:", this.props); // 正常(假设外部手动赋值)
  }
}

const child = new Child({ name: "foo" });
child.props = { name: "foo" }; // 模拟 React 的 props 注入
child.method(); // 输出:{ name: "foo" }

五、React 源码中的关键逻辑

在 React 的组件实例化过程中,即使构造函数中未传递 props,React 也会在后续步骤将 props 赋值给实例:

// React 源码简化逻辑
const instance = new Component(props); // 构造函数中可能未正确初始化 this.props
instance.props = props; // React 强制覆盖 this.props

六、总结

场景super(props)super()
父类构造函数正确接收 props,初始化 this.props接收 propsundefinedthis.props 未初始化
子类构造函数内this.props 可用this.propsundefined
其他生命周期方法this.props 正常this.props 正常(由 React 注入)

结论:在构造函数中访问 this.props 前,必须调用 super(props) 确保父类正确初始化。若不需要在构造函数中使用 props,可省略 super(props),但 React 仍会在外部注入 this.props

105.什么是FPS

FPS ≤ 15 指的是每秒帧数(Frames Per Second, FPS)小于等于 15,通常用于描述视频播放、动画或游戏的流畅度。

FPS 的意义

FPS 代表每秒显示的帧数,影响画面流畅度:

  • 30 FPS(常见):较流畅,适用于一般视频和普通应用。
  • 60 FPS(流畅):常见于高刷新率的手机、电脑动画、游戏。
  • 120 FPS 及以上(超流畅):用于电竞、VR 设备。
  • 15 FPS 及以下(卡顿):动画、视频会显得不流畅,可能有明显掉帧的情况。

FPS ≤ 15 可能出现的情况

  • 低帧率视频(如监控录像、节省带宽的视频流)。
  • 性能问题(设备性能不足,导致动画或游戏掉帧)。
  • 省电模式(某些设备在省电模式下会降低 FPS 以减少功耗)。
  • 网络或解码问题(视频流或 WebRTC 低速率传输时可能会降帧)。

如果你的项目中 FPS ≤ 15,通常意味着动画或视频播放可能卡顿,需要优化渲染效率或调整播放参数。

滚动帧率(FPS) < 10,重度卡顿的含义

滚动帧率(FPS)< 10 指的是 页面滚动时的刷新帧率低于 10 FPS,意味着滚动体验极差,出现严重的卡顿


FPS(帧率)在滚动中的作用

在前端开发中,滚动帧率(Scroll FPS)决定了页面滚动的流畅度:

  • 60 FPS(流畅):用户滚动时,每秒刷新 60 帧,动画丝滑不卡顿。
  • 30 FPS(可接受):略微有些卡顿,但大部分用户能接受。
  • 10 FPS 以下(重度卡顿):每秒刷新帧数过低,导致滚动明显“拖影”、跳帧,用户体验极差。

导致滚动帧率低的常见原因

  1. 页面渲染开销过大

    • 过多的 DOM 计算(如 offsetHeightgetBoundingClientRect() 频繁触发回流)。
    • 复杂的 CSS 样式(如 box-shadowfilterposition: fixed 等)。
    • requestAnimationFrame 未正确优化动画
  2. JavaScript 阻塞主线程

    • 大量的同步任务(如 for 循环计算、复杂的 setTimeout)。
    • 监听 scroll 事件后进行大量计算(未使用 throttlerequestAnimationFrame)。
    • 过多的事件监听器,导致滚动时浏览器需要频繁执行回调。
  3. 图片 & 资源加载问题

    • 大尺寸图片(滚动时浏览器频繁解码 & 重绘)。
    • 图片懒加载未优化,导致滚动时出现白屏或卡顿。
  4. 硬件加速未启用

    • 未使用 GPU 加速,导致所有滚动 & 动画计算都在 CPU 进行。
    • CSS will-change 未正确使用,导致 GPU 资源未提前分配。

如何优化滚动帧率?

1. 减少 DOM 复杂度
  • 减少 position: fixedbox-shadow 等高消耗 CSS。
  • 避免 table 结构,改用 flexboxgrid
2. 优化 JavaScript
  • 使用 requestAnimationFrame 进行动画和滚动相关的更新:

    let ticking = false;
    window.addEventListener("scroll", () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          // 执行滚动相关逻辑
          ticking = false;
        });
        ticking = true;
      }
    });
    
  • 使用 throttle 限制 scroll 事件触发频率:

    function throttle(func, limit) {
      let inThrottle;
      return function () {
        const args = arguments;
        const context = this;
        if (!inThrottle) {
          func.apply(context, args);
          inThrottle = true;
          setTimeout(() => (inThrottle = false), limit);
        }
      };
    }
    window.addEventListener("scroll", throttle(() => console.log("scrolling"), 100));
    
3. 图片 & 资源优化
  • 启用 懒加载 (loading="lazy")

    <img src="large-image.jpg" loading="lazy" />
    
  • 采用 WebP 等高压缩比格式,减少图片体积。

4. 启用 GPU 加速
  • 使用 will-change 提前通知浏览器优化渲染

    .smooth-scroll {
      will-change: transform;
    }
    
  • 避免 translate3d(0,0,0) 滥用,必要时使用 transform: translateZ(0); 强制 GPU 处理。


总结

  • FPS < 10 表示滚动严重卡顿,用户体验极差
  • 主要由 DOM 复杂度、JavaScript 阻塞、图片加载、CSS 样式 等问题导致。
  • 解决方案包括 减少 DOM 操作、优化 JavaScript、启用 GPU 加速、优化图片加载 等。

滚动流畅性对用户体验至关重要,前端开发中必须关注滚动帧率! 🚀

106. FPS如何计算出来

FPS(帧率, Frames Per Second) 是衡量动画、滚动、视频播放流畅度的重要指标,表示每秒钟屏幕渲染的帧数。通常,FPS 由浏览器或游戏引擎自动计算,但也可以通过 JavaScript 代码手动计算 FPS。
requestAnimationFrame结合performance.now()计算单一页面渲染fps,
滚动FPS可以监听 scroll 事件,并结合 requestAnimationFrame 计算。


FPS 计算原理

FPS = 1秒内渲染的帧数
具体计算方法:

  1. 记录当前时间戳performance.now())。
  2. 监听每一帧的绘制(使用 requestAnimationFrame)。
  3. 计算相邻两帧的时间间隔(帧间隔)。
  4. 计算 FPS = 1000 ÷ 每帧间隔时间(ms)

JavaScript 计算 FPS

下面是一个 使用 requestAnimationFrame 计算 FPS 的方法:

let lastTime = performance.now(); // 上一帧时间
let frameCount = 0; // 记录帧数
let fps = 0;

function calculateFPS() {
  let now = performance.now(); // 当前时间
  let deltaTime = now - lastTime; // 计算时间间隔
  frameCount++;

  if (deltaTime >= 1000) { // 每 1 秒更新一次 FPS
    fps = frameCount;
    console.log(`FPS: ${fps}`);
    frameCount = 0;
    lastTime = now;
  }

  requestAnimationFrame(calculateFPS); // 继续下一帧计算
}

requestAnimationFrame(calculateFPS);

📌 原理

  • 通过 requestAnimationFrame 让函数每一帧执行一次。
  • 记录帧数 frameCount,当累计时间 deltaTime 达到 1000ms(1 秒)时,计算 FPS = frameCount
  • 重置 frameCount 继续下一轮计算。

如何计算滚动 FPS(Scroll FPS)

如果要计算滚动时的 FPS,可以监听 scroll 事件,并结合 requestAnimationFrame 计算。

let lastScrollTime = performance.now();
let scrollFrameCount = 0;
let scrollFPS = 0;

function calculateScrollFPS() {
  let now = performance.now();
  let deltaTime = now - lastScrollTime;
  scrollFrameCount++;

  if (deltaTime >= 1000) {
    scrollFPS = scrollFrameCount;
    console.log(`滚动 FPS: ${scrollFPS}`);
    scrollFrameCount = 0;
    lastScrollTime = now;
  }

  requestAnimationFrame(calculateScrollFPS);
}

window.addEventListener("scroll", () => {
  requestAnimationFrame(calculateScrollFPS);
});

📌 原理

  • 监听 scroll 事件,调用 requestAnimationFrame 计算 FPS。
  • 记录 1 秒内的滚动帧数,计算 scrollFPS
  • scrollFPS 说明滚动卡顿,需要优化。

FPS 计算在不同环境下的影响

  • 理想情况

    • 屏幕刷新率一般是 60Hz,理想情况下浏览器每秒渲染 60 帧,即 60 FPS
    • 高刷设备(如 120Hz 屏幕)可能达到 120 FPS
  • 低 FPS 的原因

    • 代码执行阻塞主线程(如 setTimeoutfor 大量计算)。
    • 过多 DOM 变更导致浏览器 ReflowRepaint 过载。
    • 资源加载(图片、视频)占用渲染时间,导致掉帧。
  • 不同设备 FPS 限制

    • 低端设备可能只能维持 30 FPS 或更低。

总结

  1. FPS 计算 = 1000ms / 每帧间隔时间,可以用 requestAnimationFrame 统计。

  2. 标准 FPS

    • 60 FPS(流畅)
    • 30 FPS(普通)
    • < 15 FPS(严重卡顿)
  3. 滚动 FPS 计算 需要结合 scroll 事件监听,低 scrollFPS 表示滚动卡顿。

  4. 优化 FPS 关键点

    • 避免 JavaScript 阻塞主线程。
    • 使用 requestAnimationFrame 进行动画优化。
    • 尽量减少 DOM 操作,提高渲染效率。

🚀 想让你的页面滚动更丝滑?提高 FPS 就是关键!

107.内存占用峰值 >800MB,部分用户浏览器崩溃内存峰值如何获取内存占用值

如何获取网页的内存占用峰值

  • 1.直接谷歌浏览器F12kan Memory
  • 2.调用API performance.memory
  • 3.window.PerformanceObserver)有的浏览器可能不支持
    如果你的页面在某些用户设备上内存占用超过 800MB,导致浏览器崩溃,你可以使用以下方法来监控和获取内存峰值

1. 使用 Performance API 获取内存使用情况

performance.memory 可以获取 JS 堆内存的使用情况,但仅适用于 Chrome 浏览器(需启用实验性功能)

if (performance.memory) {
  setInterval(() => {
    console.log(`JS 堆内存使用: ${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)} MB`);
    console.log(`JS 堆内存限制: ${Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)} MB`);
  }, 5000);
} else {
  console.warn("当前浏览器不支持 performance.memory");
}

📌 说明

  • usedJSHeapSize:当前 JS 堆内存使用量(单位:字节)。
  • jsHeapSizeLimit:JS 堆的最大限制(取决于浏览器和设备)。
  • Chrome 需要在 chrome://flags/ 中启用 enable-experimental-web-platform-features 才能获取数据。

2. 使用 Performance Observer 监控内存峰值

可以通过 PerformanceObserver 监听内存使用情况,适用于 Chrome 浏览器。

if (window.PerformanceObserver) {
  const obs = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      console.log(`JS 内存使用峰值: ${Math.round(entry.usedJSHeapSize / 1024 / 1024)} MB`);
    }
  });

  obs.observe({ entryTypes: ["memory"] });
}

📌 注意

  • 这个 API 仍然是实验性的,部分浏览器可能不支持。

3. 使用 Chrome DevTools 监控内存峰值

手动分析:

  1. 打开 Chrome 开发者工具(F12 或 Ctrl+Shift+I)。
  2. 进入 “Memory”(内存) 标签页。
  3. 选择 “Heap snapshot”(堆快照)“Allocation instrumentation” 来查看内存占用情况。

4. 监听内存泄漏 & 限制

你可以手动检测并在超过 800MB 时警告用户:

setInterval(() => {
  if (performance.memory) {
    const usedMB = performance.memory.usedJSHeapSize / 1024 / 1024;
    if (usedMB > 800) {
      console.warn(`⚠️ 内存使用已达 ${usedMB} MB,可能导致崩溃!`);
    }
  }
}, 5000);

5. 使用 WebTaskManager(实验性 API)

如果需要监测所有进程的内存占用,可以尝试:

navigator.getTaskManager()
  .then(manager => manager.getProcesses())
  .then(processes => {
    for (const process of processes) {
      console.log(`进程 ID: ${process.id}, 内存占用: ${process.memory} MB`);
    }
  })
  .catch(console.error);

📌 注意:这个 API 仍在开发中,部分浏览器不支持。


总结

方法适用性支持浏览器备注
performance.memory可用(需启用实验功能)Chrome只能监测 JS 堆内存
PerformanceObserver实验性Chrome监听内存使用变化
Chrome DevTools手动分析所有现代浏览器适用于开发调试
手动检测内存峰值可用大部分现代浏览器可以设置警告
WebTaskManager API实验性部分浏览器监测整个进程

如果你的项目中内存使用 超过 800MB,可以:

  • 使用 performance.memory 定期检查内存使用
  • 在 Chrome DevTools 中手动查看内存快照
  • 优化代码,减少不必要的对象引用,防止内存泄漏

🚀 如果你的页面因高内存崩溃,建议优化 GC(垃圾回收)、减少 DOM 复杂度,并使用懒加载!

108. 首屏加载时间如何测出

如何测量首屏加载时间?

首屏加载时间(First Screen Load Time)是指网页可视区域的内容完全渲染出来的时间。一般有以下几种方法测量:


1. 使用 Performance API 测量首屏渲染

方式 1:使用 performance.timing(已废弃,但仍可用)

window.onload = function () {
  const timing = performance.timing;
  const firstScreenTime = timing.loadEventEnd - timing.navigationStart;
  console.log(`首屏加载时间: ${firstScreenTime} ms`);
};

📌 解释

  • navigationStart:页面开始加载的时间。
  • loadEventEnd:页面所有资源加载完成的时间。

⚠️ 缺点

  • performance.timing 已被废弃,在某些新浏览器上可能不准确。

方式 2:使用 PerformanceObserver 监听 first-contentful-paint

更现代的方式是使用 Performance API 监听 FCP(First Contentful Paint)LCP(Largest Contentful Paint)

if ('PerformanceObserver' in window) {
  const observer = new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
      console.log(`${entry.name}: ${entry.startTime.toFixed(2)} ms`);
    }
  });

  observer.observe({ type: "paint", buffered: true });
}

📌 解释

  • first-contentful-paint (FCP):页面首次有内容渲染的时间。
  • largest-contentful-paint (LCP):首屏主要内容完全渲染的时间。

2. 使用 Chrome DevTools 进行测量

如果想手动测量:

  1. 打开 Chrome 开发者工具(F12 或 Ctrl + Shift + I

  2. 进入 Performance(性能) 面板。

  3. 点击 录制 按钮,然后加载页面。

  4. 观察 “Timings” 选项,查看:

    • FCP(First Contentful Paint)
    • LCP(Largest Contentful Paint)
    • DOMContentLoaded(HTML 解析完成时间)
    • Load Event(所有资源加载完成时间)

3. 使用 requestAnimationFrame 监测首屏渲染完成

如果你的页面包含异步数据渲染(如 Vue/React),可以使用 requestAnimationFrame 计算 页面首屏真实渲染时间

let firstScreenRendered = false;

function checkRenderTime() {
  if (!firstScreenRendered) {
    firstScreenRendered = true;
    console.log(`首屏渲染时间: ${performance.now().toFixed(2)} ms`);
  }
}

requestAnimationFrame(checkRenderTime);

📌 适用场景

  • 适用于 单页应用(SPA)
  • 监听 Vue/React 页面何时真正渲染完毕

4. 使用 MutationObserver 监听 DOM 变化

如果你的页面是动态渲染的(如 Vue/React 通过 v-ifsetState 渲染),可以监听首屏 DOM 变化。

const observer = new MutationObserver(() => {
  console.log(`首屏 DOM 加载完成: ${performance.now().toFixed(2)} ms`);
  observer.disconnect(); // 监听一次即可
});

observer.observe(document.body, { childList: true, subtree: true });

📌 适用场景

  • 适用于 前端渲染页面(CSR) ,如 Vue、React、UniApp。

5. 使用 Web Vitals 监控首屏加载

如果想获得更详细的首屏时间数据,可以使用 web-vitals 库:

npm install web-vitals
import { getLCP, getFCP } from "web-vitals";

getLCP((metric) => console.log(`LCP: ${metric.value} ms`));
getFCP((metric) => console.log(`FCP: ${metric.value} ms`));

📌 适用于

  • 监控用户真实的加载体验
  • Vue、React 等前端框架

总结

方法适用性适用场景备注
performance.timing过时传统 HTML 页面仅适用于同步渲染
PerformanceObserver 监听 FCP/LCP推荐现代 Web、SSR、Vue、React适用于所有场景
Chrome DevTools手动测量开发调试适用于任何网站
requestAnimationFrame推荐Vue、React 单页应用监听真实的渲染时间
MutationObserver适用动态渲染页面监听 DOM 变化
web-vitals推荐监控真实用户体验获取更精确的首屏指标

🚀 最佳实践

  • SSR 页面(如 Nuxt、Next.js):使用 PerformanceObserver 监听 LCP
  • Vue/React(CSR 单页应用):使用 requestAnimationFrameMutationObserver 监听渲染完成。
  • 开发调试:使用 Chrome DevTools Performance 面板 直接测量。

🔥 想优化首屏加载?减少关键资源、懒加载图片、使用 CDN 加速是关键! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

傻小胖

如果觉得不错就给点赏钱吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值