前端性能优化全攻略:JavaScript 优化、DOM 操作、内存管理、资源压缩与合并、构建工具及性能监控

#JavaScript性能优化实战#

1 为什么需要性能优化?

1.1 性能优化的核心价值:用户体验与业务指标

        性能优化不仅是技术层面的追求,更是直接影响用户体验和业务成败的关键因素。

  • 用户体验(UX)
    • 响应速度:用户期望页面加载时间不超过 3 秒(根据 Google 研究,53% 的移动用户会因加载超过 3 秒而放弃访问)。
    • 流畅性:卡顿、延迟会显著降低用户满意度,例如动画帧率低于 60FPS 时,人眼可感知到不流畅。
    • 交互反馈:即时响应用户操作(如点击、输入)能增强信任感。
  • 业务指标
    • 转化率:性能每提升 1 秒,转化率可能提高 7%(Akamai 数据)。
    • 留存率:加载时间过长会导致用户流失,尤其在移动端。
    • SEO 排名:Google 将页面速度纳入搜索排名算法,性能差可能直接影响流量。

        案例:某电商网站通过优化首屏加载时间从 5 秒降至 1.5 秒,转化率提升 12%。

1.2 JavaScript 性能瓶颈的常见场景

        JavaScript 作为前端交互的核心语言,其性能问题可能出现在多个环节:

  • 代码执行效率低
    • 频繁操作 DOM(如循环中直接修改 innerHTML)。
    • 复杂算法或递归调用导致主线程阻塞。
  • 内存泄漏
    • 闭包、定时器、事件监听未清理,导致内存占用持续增长。
    • 全局变量意外持久化,无法被垃圾回收。
  • 异步处理不当
    • 回调地狱导致代码难以维护,且可能引发竞态条件。
    • 未合理使用 Promise 或 async/await,导致任务排队延迟。
  • 资源加载阻塞
    • 未压缩的 JavaScript 文件体积过大,阻塞页面渲染(尤其是首屏)。
    • 第三方脚本(如广告、分析工具)加载缓慢,拖慢整体性能。

典型场景示例

  • 滚动列表时频繁触发重排(Reflow)和重绘(Repaint)。
  • 动画未使用硬件加速(如 transform),导致帧率下降。

1.3 性能优化的基本原则

        性能优化并非盲目追求极致速度,而是需遵循科学方法论:

  • 80/20法则(帕累托法则)
    • 80% 的性能问题可能由 20% 的代码引起。优先通过性能分析工具(如 Chrome DevTools)定位瓶颈。
    • 示例:优化首屏渲染的关键路径,而非全局代码。
  • 渐进增强(Progressive Enhancement)
    • 确保基础功能在低性能环境下可用,再逐步增强体验。
    • 示例:优先加载核心 CSS 和 JS,非关键资源延迟加载。
  • 权衡取舍
    • 性能 vs 可维护性:避免过度优化导致代码难以维护。
    • 示例:使用 WebAssembly 提升计算性能,但需评估开发成本。
  • 持续监控
    • 性能优化是迭代过程,需结合用户反馈和数据分析持续调整。

原则实践

  • 懒加载(Lazy Loading):仅在用户需要时加载资源(如图片、模块)。
  • 代码分割(Code Splitting):通过 Webpack 等工具将代码拆分为更小的块,按需加载。

2 代码层面的优化

2.1 变量与数据类型优化

2.1.1 避免全局变量污染

  • 问题:全局变量会污染全局命名空间,导致命名冲突和难以追踪的错误。
  • 解决方案
    • 使用立即执行函数表达式(IIFE)或模块化(ES6 模块、CommonJS)封装代码。
    • 避免在全局作用域中声明变量。
// 不推荐:全局变量污染
var counter = 0;

// 推荐:使用IIFE封装
(function() {
  let counter = 0;
  function increment() {
    counter++;
    console.log(counter);
  }
  increment(); // 输出1
})();

2.1.2 使用 const 和 let 替代 var

  • 原因
    • var 存在变量提升问题,可能导致意外行为。
    • const 和 let 具有块级作用域,更安全且易维护。
  • 最佳实践
    • 优先使用 const(除非需要重新赋值)。
    • 仅在需要可变变量时使用 let。
// 不推荐:使用var
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000); // 输出3次3
}

// 推荐:使用let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000); // 输出0, 1, 2
}

2.1.3 合理使用数据类型

  • Number vs BigInt
    • 使用 Number 处理常规数值,BigInt 处理超大整数(注意:BigInt 与 Number 不能直接混用)。
  • String 拼接优化
    • 使用模板字符串(Template Literals)替代 + 拼接,提高可读性。
    • 大量拼接时,使用 Array.join() 替代 +=。
// 不推荐:字符串拼接
let str = '';
for (let i = 0; i < 1000; i++) {
  str += i; // 每次拼接都会创建新字符串
}

// 推荐:使用数组join
let parts = [];
for (let i = 0; i < 1000; i++) {
  parts.push(i);
}
let str = parts.join('');

2.2 作用域与闭包优化

2.2.1 减少作用域链查找深度

  • 问题:深层作用域链查找会降低性能。
  • 解决方案
    • 将频繁访问的变量缓存为局部变量。
// 不推荐:深层作用域链查找
function process(data) {
  for (let i = 0; i < data.items.length; i++) {
    console.log(data.items[i]); // 每次循环都会查找data.items
  }
}

// 推荐:缓存局部变量
function process(data) {
  let items = data.items;
  for (let i = 0; i < items.length; i++) {
    console.log(items[i]);
  }
}

2.2.2 警惕闭包导致的内存泄漏

  • 问题:闭包会引用外部变量,可能导致内存无法释放。
  • 解决方案
    • 在不需要时手动解除引用(如将变量设为 null)。
function createClosure() {
  let largeData = new Array(1000000).fill('data');
  return function() {
    console.log(largeData.length);
  };
}

let closure = createClosure();
// 使用后手动解除引用
closure = null;

2.3 循环与迭代优化

2.3.1 替代 for...in 的高效迭代方式

  • 问题:for...in 会遍历可枚举属性,包括原型链上的属性,性能较低。
  • 解决方案
    • 使用 for...of(适用于数组和可迭代对象)。
    • 使用 Array.prototype.forEach(适用于数组)。
// 不推荐:for...in遍历数组
let arr = [1, 2, 3];
for (let key in arr) {
  console.log(arr[key]); // 可能遍历到非数字属性
}

// 推荐:for...of
for (let value of arr) {
  console.log(value);
}

2.3.2 缓存数组长度

  • 原因:每次循环都会重新计算数组长度,影响性能。
// 不推荐:未缓存长度
for (let i = 0; i < arr.length; i++) {
  // ...
}

// 推荐:缓存长度
for (let i = 0, len = arr.length; i < len; i++) {
  // ...
}

2.3.3 倒序循环减少边界检查

  • 原因:倒序循环(i--)在某些引擎中可能优化边界检查。
for (let i = arr.length - 1; i >= 0; i--) {
  // ...
}

2.4 函数优化

2.4.1 避免内联函数重复定义

  • 问题:内联函数在每次调用时都会重新创建。
  • 解决方案
    • 将函数提取到外部作用域。
// 不推荐:内联函数
arr.forEach(item => {
  let process = v => v * 2;
  console.log(process(item));
});

// 推荐:提取函数
function process(v) {
  return v * 2;
}
arr.forEach(item => console.log(process(item)));

2.4.2 使用箭头函数与绑定 this 的优化

  • 原因:箭头函数不会创建自己的 this,适合在回调中使用。
// 不推荐:使用普通函数绑定this
function Counter() {
  this.count = 0;
  setTimeout(function() {
    console.log(this.count); // undefined
  }, 1000);
}

// 推荐:使用箭头函数
function Counter() {
  this.count = 0;
  setTimeout(() => {
    console.log(this.count); // 0
  }, 1000);
}

2.4.3 递归改迭代(尾递归优化)

  • 问题:递归可能导致栈溢出,且性能较低。
  • 解决方案
    • 使用迭代替代递归。
    • 在支持尾递归优化的环境中,使用尾递归。
// 不推荐:普通递归
function factorial(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
}

// 推荐:迭代替代递归
function factorial(n) {
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
  return result;
}

3 异步与事件处理优化

3.1 异步编程的最佳实践

3.1.1 使用 Promise 替代回调地狱

  • 问题:回调地狱(Callback Hell)导致代码难以维护,错误处理复杂。
  • 解决方案:使用 Promise 链式调用,简化异步逻辑。
// 不推荐:回调地狱
fetchData(url, (err, data) => {
  if (err) return console.error(err);
  processData(data, (err, result) => {
    if (err) return console.error(err);
    console.log(result);
  });
});

// 推荐:使用Promise
fetchData(url)
  .then(data => processData(data))
  .then(result => console.log(result))
  .catch(err => console.error(err));

3.1.2 async/await 的合理使用与错误处理

  • 优势:async/await 使异步代码更接近同步逻辑,易读性更强。
  • 最佳实践
    • 使用 try/catch 捕获错误。
    • 避免在循环中直接调用 await(可能导致性能下降)。
// 推荐:使用async/await
async function fetchAndProcess() {
  try {
    const data = await fetchData(url);
    const result = await processData(data);
    console.log(result);
  } catch (err) {
    console.error(err);
  }
}

// 注意:避免在循环中直接await
async function processArray(items) {
  const results = [];
  for (const item of items) {
    // 改用Promise.all优化
    results.push(processItem(item));
  }
  return Promise.all(results);
}

3.2 事件监听与委托

3.2.1 事件委托减少绑定数量

  • 问题:为每个子元素绑定事件会导致性能开销和内存泄漏。
  • 解决方案:利用事件冒泡,在父元素上绑定事件,通过 event.target 识别触发源。
// 不推荐:为每个按钮绑定事件
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick);
});

// 推荐:事件委托
document.querySelector('.button-container').addEventListener('click', event => {
  if (event.target.matches('.btn')) {
    handleClick(event);
  }
});

3.2.2 防抖(Debounce)与节流(Throttle)的实现

  • 防抖(Debounce):延迟执行函数,直到一段时间内不再触发。
  • 节流(Throttle):限制函数在一定时间内只执行一次。
  • 应用场景
    • 防抖:搜索框输入、窗口调整大小。
    • 节流:滚动事件、鼠标移动。
// 防抖实现
function debounce(func, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), delay);
  };
}

// 节流实现
function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function(...args) {
    const context = this;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}

// 使用示例
window.addEventListener('resize', debounce(() => console.log('Resized!'), 300));
window.addEventListener('scroll', throttle(() => console.log('Scrolled!'), 100));

3.3 定时器优化

3.3.1 避免 setTimeout 嵌套

  • 问题:setTimeout 嵌套可能导致回调地狱,逻辑混乱。
  • 解决方案:使用 async/await 或 Promise 链式调用替代。
// 不推荐:setTimeout嵌套
setTimeout(() => {
  console.log('Step 1');
  setTimeout(() => {
    console.log('Step 2');
    setTimeout(() => {
      console.log('Step 3');
    }, 1000);
  }, 1000);
}, 1000);

// 推荐:使用Promise链
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function executeSteps() {
  console.log('Step 1');
  await delay(1000);
  console.log('Step 2');
  await delay(1000);
  console.log('Step 3');
}
executeSteps();

3.3.2 使用 requestAnimationFrame 优化动画性能

  • 问题:setTimeout 或 setInterval 用于动画可能导致掉帧或性能不佳。
  • 解决方案:使用 requestAnimationFrame,浏览器会在下一次重绘前调用回调函数。
// 不推荐:使用setInterval
let start;
function animate(timestamp) {
  if (!start) start = timestamp;
  const progress = timestamp - start;
  const element = document.querySelector('.box');
  element.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;
  if (progress < 2000) { // 动画持续2秒
    setTimeout(animate, 16); // 约60FPS
  }
}
setTimeout(animate, 16);

// 推荐:使用requestAnimationFrame
let start;
function animate(timestamp) {
  if (!start) start = timestamp;
  const progress = timestamp - start;
  const element = document.querySelector('.box');
  element.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;
  if (progress < 2000) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

4 DOM 操作优化

4.1 减少 DOM 操作频率

4.1.1 批量修改 DOM(DocumentFragment)

  • 问题:每次修改 DOM 都会触发重绘或重排,频繁操作会导致性能下降。
  • 解决方案:使用 DocumentFragment 或批量操作,减少 DOM 操作的次数。
// 不推荐:逐个添加节点
const list = document.querySelector('#list');
for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  item.textContent = `Item ${i}`;
  list.appendChild(item); // 每次操作都会触发重绘/重排
}

// 推荐:使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  item.textContent = `Item ${i}`;
  fragment.appendChild(item);
}
list.appendChild(fragment); // 一次性插入,减少重绘/重排

4.1.2 使用 innerHTML 的注意事项

  • 优势:innerHTML 可以一次性插入大量 HTML,性能较高。
  • 注意事项
    • 避免插入用户生成的内容,防止 XSS 攻击。
    • 插入复杂 HTML 时,浏览器需要解析字符串,可能影响性能。
// 使用 innerHTML 插入 HTML
const container = document.querySelector('#container');
const htmlString = '<div>Item 1</div><div>Item 2</div>';
container.innerHTML = htmlString;

// 安全处理用户输入
function sanitizeInput(input) {
  const div = document.createElement('div');
  div.textContent = input; // 转义特殊字符
  return div.innerHTML;
}
const userInput = '<script>alert("XSS")</script>';
container.innerHTML = `<div>${sanitizeInput(userInput)}</div>`;

4.2 CSS 选择器优化

4.2.1 避免复杂选择器(如通配符 *、深层嵌套)

  • 问题:复杂选择器会增加浏览器解析和匹配的时间。
  • 解决方案
    • 尽量避免使用通配符 * 和深层嵌套选择器。
    • 使用更具体的选择器,减少匹配范围。
/* 不推荐:复杂选择器 */
div ul li a {
  color: red;
}

/* 推荐:更具体的选择器 */
.nav-link {
  color: red;
}

4.2.2 缓存 DOM 查询结果

  • 问题:重复查询 DOM 会导致性能开销。
  • 解决方案:将频繁使用的 DOM 节点缓存到变量中。
// 不推荐:重复查询
function updateText() {
  document.querySelector('#myDiv').textContent = 'Updated';
  document.querySelector('#myDiv').style.color = 'blue';
}

// 推荐:缓存查询结果
function updateText() {
  const myDiv = document.querySelector('#myDiv');
  myDiv.textContent = 'Updated';
  myDiv.style.color = 'blue';
}

4.3 虚拟 DOM 与框架优化

4.3.1 React/Vue 中的 key 优化

  • 问题:在列表渲染中,缺少 key 或使用不稳定的 key 会导致组件重渲染或性能下降。
  • 解决方案
    • 使用唯一且稳定的 key,如 ID。
    • 避免使用数组索引作为 key。
// 不推荐:使用索引作为 key
{items.map((item, index) => (
  <div key={index}>{item.name}</div>
))}

// 推荐:使用唯一 ID 作为 key
{items.map(item => (
  <div key={item.id}>{item.name}</div>
))}

4.3.2 避免不必要的组件重渲染

  • 问题:组件的 props 或 state 变化会触发重渲染,可能导致性能问题。
  • 解决方案
    • 使用 React.memo(React)或 vue 的 computed 属性(Vue)优化渲染。
    • 确保 props 和 state 的变化是必要的。
// 不推荐:每次父组件渲染都会重渲染子组件
function Child({ value }) {
  console.log('Rendering...');
  return <div>{value}</div>;
}

// 推荐:使用 React.memo 避免不必要的重渲染
const Child = React.memo(({ value }) => {
  console.log('Rendering...');
  return <div>{value}</div>;
});

5 内存管理与垃圾回收

5.1 内存泄漏的常见场景

5.1.1 闭包、定时器、事件监听未清理

  • 问题:闭包、定时器、事件监听器等会持有对外部变量的引用,如果未正确清理,会导致内存泄漏。
  • 解决方案
    • 在不需要时手动清理定时器、事件监听器。
    • 避免不必要的闭包引用。
// 不推荐:定时器未清理
function startTimer() {
  const id = setInterval(() => {
    console.log('Running');
  }, 1000);
  // 缺少 clearInterval(id)
}

// 推荐:清理定时器
let id;
function startTimer() {
  id = setInterval(() => {
    console.log('Running');
  }, 1000);
}
function stopTimer() {
  clearInterval(id);
}

// 不推荐:事件监听器未清理
function setupEventListener() {
  window.addEventListener('resize', handleResize);
  // 缺少移除监听器
}

// 推荐:清理事件监听器
function setupEventListener() {
  function handleResize() {
    console.log('Resized');
  }
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}
const removeListener = setupEventListener();
removeListener(); // 清理监听器

5.1.2 全局变量意外持久化

  • 问题:未声明的变量会隐式成为全局变量,导致内存无法释放。
  • 解决方案
    • 使用 let 或 const 声明变量。
    • 避免在全局作用域中定义不必要的变量。
// 不推荐:隐式全局变量
function leakyFunction() {
  leak = 'This is a leak'; // 未声明,成为全局变量
}

// 推荐:使用 let 或 const
function nonLeakyFunction() {
  const noLeak = 'This is not a leak';
}

5.2 垃圾回收机制

5.2.1 标记清除(Mark-and-Sweep)

  • 原理
    1. 垃圾回收器从根对象(如全局对象)开始,标记所有可达对象。
    2. 清除未标记的对象,释放内存。
  • 优点:能够回收循环引用对象。
  • 缺点:标记清除过程会暂停程序执行(暂停时间取决于堆的大小)。

5.2.2 引用计数(Reference Counting)

  • 原理:每个对象维护一个引用计数,当引用计数为 0 时,对象被回收。
  • 优点:回收及时,不需要暂停程序。
  • 缺点:无法回收循环引用对象。

5.2.3 V8 引擎的垃圾回收

  • V8 引擎使用分代回收策略:
    • 新生代:存活时间短的对象,使用 Scavenge 算法(复制清除)。
    • 老生代:存活时间长的对象,使用 Mark-Sweep 和 Mark-Compact 算法。

5.3 弱引用(WeakMap、WeakSet)的使用

5.3.1 WeakMap

  • 特点
    • 键必须是对象,值可以是任意类型。
    • 不会阻止键被垃圾回收。
  • 使用场景:缓存、私有属性存储。
const weakMap = new WeakMap();
let obj = { name: 'Temp' };
weakMap.set(obj, 'Some value');
console.log(weakMap.get(obj)); // 输出: 'Some value'
obj = null; // 允许垃圾回收

5.3.2 WeakSet

  • 特点
    • 只能存储对象,不能存储值。
    • 不会阻止对象被垃圾回收。
  • 使用场景:存储对象的弱引用集合。
const weakSet = new WeakSet();
let obj = { name: 'Temp' };
weakSet.add(obj);
console.log(weakSet.has(obj)); // 输出: true
obj = null; // 允许垃圾回收

5.4 内存分析工具

5.4.1 Chrome DevTools 内存快照

  • 功能
    • 拍摄堆快照,分析内存使用情况。
    • 检测内存泄漏、对象引用关系。
  • 使用步骤
    1. 打开 Chrome DevTools,进入 "Memory" 面板。
    2. 点击 "Take heap snapshot" 拍摄快照。
    3. 分析快照中的对象,查找未释放的内存。

5.4.2 使用 performance.memory(Node.js 环境)

  • 功能
    • 提供内存使用信息,包括总内存、堆内存等。
if (performance.memory) {
  console.log('Total JS Heap Size:', performance.memory.totalJSHeapSize);
  console.log('Used JS Heap Size:', performance.memory.usedJSHeapSize);
  console.log('JS Heap Size Limit:', performance.memory.jsHeapSizeLimit);
} else {
  console.log('performance.memory is not supported in this environment.');
}

6 网络请求与资源加载优化

6.1 使用 Webpack/Rollup 进行代码分割

6.1.1 代码分割的意义

  • 问题:单个 JavaScript 文件过大会导致加载时间过长。
  • 解决方案:通过代码分割,将代码拆分为多个小块,按需加载。

6.1.2 Webpack 代码分割

  • 动态导入:使用 import() 实现按需加载。
  • 配置优化:通过 SplitChunksPlugin 自动分割公共代码。
// 动态导入示例
function loadComponent() {
  import('./component.js').then(module => {
    const component = module.default;
    document.body.appendChild(component());
  });
}

// Webpack 配置示例
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 自动分割所有模块
    },
  },
};

6.1.3 Rollup 代码分割

  • 手动分割:通过 output.manualChunks 配置。
// Rollup 配置示例
export default {
  input: 'src/main.js',
  output: {
    dir: 'dist',
    format: 'esm',
    manualChunks(id) {
      if (id.includes('node_modules')) {
        return 'vendor'; // 将第三方库打包到 vendor.js
      }
    },
  },
};

6.2 Gzip/Brotli 压缩

6.2.1 Gzip 压缩

  • 原理:通过压缩算法减少文件体积。
server {
  gzip on;
  gzip_types text/plain application/javascript text/css;
  gzip_min_length 1024;
}

6.2.2 Brotli 压缩

  • 优势:比 Gzip 压缩率更高。
server {
  brotli on;
  brotli_types text/plain application/javascript text/css;
  brotli_min_length 1024;
}

6.2.3 压缩效果对比

文件类型原始大小Gzip 压缩后Brotli 压缩后
HTML10 KB2.5 KB2.0 KB
JavaScript100 KB25 KB20 KB
CSS20 KB5 KB4 KB

6.3 懒加载与预加载

6.3.1 懒加载

  • 原理:延迟加载非关键资源,减少初始加载时间。

  • 图片懒加载:使用 loading="lazy" 属性。

<img src="image.jpg" alt="Lazy Loaded Image" loading="lazy">
  • 动态导入懒加载:结合 Webpack 的动态导入。
function loadModule() {
  import('./module.js').then(module => {
    module.init();
  });
}

6.3.2 预加载

  • 原理:提前加载关键资源,提升用户体验。

  • 使用 <link rel="preload">:

<link rel="preload" href="critical.js" as="script">
  • 预加载字体
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin="anonymous">

6.4 图片懒加载(loading="lazy")

6.4.1 优势

  • 减少初始页面加载时间。
  • 节省带宽,提升性能。

6.4.2 浏览器支持

  • 现代浏览器(Chrome、Edge、Firefox 等)已支持 loading="lazy"。
<img src="image.jpg" alt="Image" loading="lazy" onerror="this.onerror=null;this.src='fallback.jpg';">

6.5 预加载关键资源(<link rel="preload">)

6.5.1 使用场景

  • 预加载关键 JavaScript、CSS、字体等资源。
  • 提升首屏渲染速度。

6.5.2 注意事项

  • 避免预加载过多资源,导致带宽浪费。
  • 结合 as 属性指定资源类型:
<link rel="preload" href="styles.css" as="style">
<link rel="preload" href="main.js" as="script">

6.6 Service Worker 与缓存策略

6.6.1 Service Worker 基础

  • 作用:拦截网络请求,实现离线缓存和资源更新。
  • 注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js').then(registration => {
    console.log('Service Worker registered with scope:', registration.scope);
  });
}

6.6.2 缓存策略

  • 缓存优先(Cache-First):优先从缓存读取资源。
  • 网络优先(Network-First):优先从网络获取资源,失败时回退到缓存。
  • 示例(Cache-First)
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      return cachedResponse || fetch(event.request);
    })
  );
});

6.7 离线缓存与资源更新

6.7.1 离线缓存

  • 实现:通过 Service Worker 缓存关键资源,实现离线访问。
const CACHE_NAME = 'static-cache-v1';
const urlsToCache = ['/index.html', '/styles.css', '/main.js'];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(urlsToCache);
    })
  );
});

6.7.2 资源更新

  • 问题:缓存资源可能过期,需要更新。
  • 解决方案:使用版本号控制缓存。
const CACHE_NAME = 'static-cache-v2'; // 更新版本号

6.8 缓存失效策略(如版本号控制)

6.8.1 版本号控制

  • 原理:通过修改缓存名称,强制更新缓存。
  • 实现:在 Service Worker 中使用动态版本号。
const VERSION = 'v2'; // 版本号
const CACHE_NAME = `static-cache-${VERSION}`;

6.8.2 哈希值控制

  • 原理:根据文件内容生成哈希值,确保缓存唯一性。
  • 工具:使用 Webpack 的 [hash] 或 [chunkhash]。
output: {
  filename: '[name].[contenthash].js', // 根据内容生成哈希值
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Thanks_ks

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值