经验笔记:前端堆栈分配

embedded/2024/9/25 19:14:20/

前端堆栈分配经验笔记

概述

前端开发中,“堆栈分配”通常不是一个直接涉及的概念,因为现代前端开发语言如JavaScript已经很大程度上抽象掉了底层的内存管理。然而,理解JavaScript中的内存管理机制对于避免内存泄漏和优化应用性能至关重要。本文档将探讨前端中的内存管理基础知识,以及如何避免内存泄漏和优化内存使用。

一、JavaScript中的内存管理

1. 栈内存 (Stack Memory)

栈内存主要用于存储函数调用的信息,如函数参数、局部变量及函数调用的返回地址等。栈内存的特点是先进后出(LIFO),由操作系统自动分配和回收,无需开发者干预。栈内存的分配和回收速度快,但容量有限。

2. 堆内存 (Heap Memory)

堆内存用于存储对象、数组等复杂数据结构。堆内存是动态分配的,由JavaScript引擎负责管理。与栈内存不同,堆内存中的对象可以随时创建和销毁,且对象之间的引用关系较为复杂。

二、如何避免内存泄漏

避免内存泄漏是保证应用程序性能稳定的关键之一。内存泄漏指的是程序中已分配的内存未被正确释放或回收,导致随着时间推移,可用内存越来越少,最终可能导致系统变慢甚至崩溃。在前端开发中,主要使用JavaScript编写代码,而JavaScript拥有自动的垃圾回收机制,但这并不意味着不会发生内存泄漏。以下是一些避免内存泄漏的最佳实践:

1. 释放不再使用的对象
  • 解除事件监听器:确保在元素不再需要监听事件时解除绑定。例如,在组件卸载时取消事件监听。

    element.removeEventListener('click', handleClick);
    
  • 清理定时器:清除任何设置的定时器或间隔函数,防止它们继续占用内存。

    clearInterval(intervalId);
    clearTimeout(timeoutId);
    
2. 闭包
  • 谨慎使用闭包:闭包可以使内部函数访问外部作用域中的变量,但如果闭包长期持有对大对象的引用,则可能导致内存泄漏。
    • 尽量减少闭包的使用,或者确保闭包不会长时间保持对大对象的引用。
3. DOM 元素
  • 解除 DOM 引用:如果一个DOM节点被从文档中移除,同时确保其事件监听器和其他相关的引用也被清理。
    function removeElement(element) {element.parentNode.removeChild(element);element.removeEventListener('click', handler); // 清除事件监听器
    }
    
4. 单例和服务
  • 单例模式和服务:确保单例和服务实例在不需要时被销毁或释放。
    • 如果服务依赖于外部资源,确保在服务不再使用时释放这些资源。
5. 图片和媒体资源
  • 懒加载:对于图片和视频等媒体资源,使用懒加载技术可以减少初始加载时内存的使用。
    • 只在需要显示的时候才加载资源,这样可以避免不必要的内存占用。
6. 使用工具检测
  • 利用开发者工具:大多数现代浏览器都提供了开发者工具,如Chrome DevTools,可以用来检测和定位内存泄漏。
    • 使用Performance面板中的Memory选项卡来查看内存使用情况。
    • Heap Snapshot功能可以帮助分析对象的引用链,找出泄露的原因。
7. 循环引用

三、监控前端开发中的内存使用情况

监控前端开发中的内存使用情况是维护应用性能和用户体验的重要手段。虽然现代浏览器和JavaScript引擎在内存管理方面做了大量的工作,但作为开发者,仍然需要密切关注应用的内存使用情况,以便及时发现和解决问题。以下是几种常用的监控前端内存使用的方法:

1. 使用浏览器开发者工具(Developer Tools)

大多数现代浏览器都内置了开发者工具,如Chrome DevTools,Firefox Developer Tools等,这些工具提供了丰富的功能来帮助开发者监控和诊断内存使用情况。

  • Performance 面板:可以查看应用的整体性能,包括CPU使用率、内存使用情况等。点击Memory选项卡可以看到实时的JS Heap Size,即JavaScript堆内存大小。

  • Memory 面板:提供了详细的内存使用统计信息,包括JS heap size、DOM节点数量等。还可以使用Heap Snapshot功能来获取应用内存的快照,分析内存使用情况。

2. 使用Performance Monitor插件

除了浏览器自带的开发者工具外,还有一些第三方插件可以帮助你更深入地监控内存使用情况。例如,Performance Monitor是一个Chrome扩展,它可以提供更多的内存使用统计数据。

3. 使用console.log()

虽然不是最精确的方法,但在关键代码路径中添加console.log()语句可以帮助开发者追踪特定时刻的内存使用情况。例如,可以在每次执行某个耗内存的操作前后记录内存使用情况。

4. 使用window.performance.memory API

window.performance.memory API提供了关于JavaScript内存使用的基本信息,包括JS heap的大小等。这是一个简单的方法来获取内存使用情况的概览。

console.log(window.performance.memory);
5. 使用Webpack Bundle Analyzer

如果你的应用使用Webpack打包,可以考虑使用Webpack Bundle Analyzer插件来分析构建后的包大小。这有助于识别哪些模块占用了较多的内存。

6. 使用库和框架提供的工具

许多流行的前端框架和库提供了自己的内存监控工具或插件。例如React的React DevTools可以让你检查React组件树的状态和性能。

7. 定期审查代码

定期审查代码以确保没有内存泄漏的情况出现。检查是否有未清理的事件监听器、未销毁的定时器等,这些都是常见的内存泄漏来源。

8. 使用自动化工具

可以设置自动化测试和构建流程,利用诸如Jest、Mocha等测试框架,结合内存监控工具来确保每次发布前都能检测到内存使用情况。

9. 性能基线测试

建立应用的性能基线,定期进行性能测试并与基线对比,发现异常时及时调查原因。

四、性能优化策略

1. 减少HTTP请求
  • 合并文件:将多个CSS或JavaScript文件合并成一个,以减少HTTP请求的数量。
  • 使用雪碧图:将多个小图标合并在一张图片中,以减少图片请求次数。
  • 使用Web字体格式:使用.woff.woff2格式的字体文件,它们体积较小,加载更快。
2. 优化图片
  • 压缩图片:使用工具如TinyPNG或ImageOptim来压缩图片,减小文件大小。
  • 选择合适的格式:根据图片内容选择合适的格式,如JPEG适合照片,PNG适合透明背景的图像,SVG适合矢量图形。
  • 懒加载:延迟加载不在视口内的图片,直到它们进入视口后再加载。
3. 代码优化
  • 压缩和最小化:使用工具如UglifyJS或Terser来压缩JavaScript文件,使用CSSNano来压缩CSS文件。
  • 去除无用代码:使用Tree Shaking技术去除未使用的代码。
  • 使用CDN:将静态资源部署在内容分发网络上,以加速资源的加载速度。
4. 利用缓存
  • 浏览器缓存:设置合适的HTTP缓存头,如Cache-ControlExpires,让浏览器可以有效利用缓存。

  • Service Worker:使用Service Worker来缓存静态资源,提供离线访问支持。

    • Service Worker 是一种特殊的 JavaScript 脚本,它运行在一个独立的线程中,可以在客户端浏览器后台执行,即使用户关闭了网页标签页。Service Worker 的主要功能是拦截网络请求,管理和缓存资源,以及推送通知。它们为Web应用程序带来了类似原生应用的功能,如离线访问、推送通知和预加载策略。
    Service Worker 的特点
    1. 离线访问:Service Worker 可以缓存网页资源,使得应用可以在没有网络连接的情况下访问。
    2. 拦截网络请求:Service Worker 可以拦截和控制资源的加载,允许开发者决定资源是从网络还是从缓存中加载。
    3. 推送通知:即使用户没有打开应用,也可以发送推送通知。
    4. 背景同步:允许应用在后台进行数据同步,例如上传照片或更新数据。
    5. 预加载:允许预先加载资源,以提高后续加载速度。
    Service Worker 的工作原理
    1. 注册:首先需要注册一个 Service Worker,注册脚本通常位于主页面的 JavaScript 文件中。

      if ('serviceWorker' in navigator) {window.addEventListener('load', function() {navigator.serviceWorker.register('/service-worker.js').then(function(registration) {console.log('ServiceWorker registration successful with scope:', registration.scope);}, function(err) {console.log('ServiceWorker registration failed:', err);});});
      }
      
    2. 生命周期:Service Worker 有几个关键的生命周期阶段:

      • 安装 (Install):当 Service Worker 脚本首次下载时触发。此时可以开始缓存静态资源。
      • 激活 (Activate):当 Service Worker 完成安装并且旧版本的 Service Worker 已经卸载后触发。此时可以清理旧的缓存,并告诉浏览器使用新的 Service Worker。
      • 等待 (Waiting):新版本 Service Worker 已经安装但尚未激活的状态。
      • 控制 (Controlling):Service Worker 开始控制页面的加载请求。
    3. 拦截请求:Service Worker 可以通过监听 fetch 事件来拦截和处理网络请求。

      self.addEventListener('fetch', function(event) {event.respondWith(caches.match(event.request).then(function(response) {return response || fetch(event.request);}));
      });
      
    Service Worker 的应用场景
    • 离线应用:使 Web 应用能够在没有互联网连接的情况下正常工作。
    • 加速加载:通过缓存经常访问的资源来提高加载速度。
    • 背景同步:在设备重新连接到网络时自动同步数据。
    • 推送通知:允许 Web 应用向用户发送推送通知,即使用户没有打开应用。
    注册和使用示例
    1. 注册 Service Worker
      在主页面的 JavaScript 文件中注册 Service Worker。

      if ('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js').then(registration => console.log('SW registered: ', registration)).catch(error => console.log('SW registration failed: ', error));});
      }
      
    2. Service Worker 脚本
      创建一个名为 sw.js 的 Service Worker 脚本。

      const CACHE_NAME = 'my-site-cache-v1';
      const urlsToCache = ['/','/index.html','/styles.css','/script.js'
      ];// 安装阶段
      self.addEventListener('install', event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => {return cache.addAll(urlsToCache);}));
      });// 激活阶段
      self.addEventListener('activate', event => {event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (cacheName !== CACHE_NAME) {return caches.delete(cacheName);}}));}));
      });// 拦截请求
      self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(response => {if (response) {return response;}return fetch(event.request);}));
      });
      

    通过使用 Service Worker,Web 应用可以获得更好的性能、更高的可靠性和更好的用户体验。

5. 提升渲染性能
  • 避免重排和重绘:减少DOM操作,使用CSS3变换而不是改变样式属性来实现动画效果。

    • 避免重排(reflow)和重绘(repaint)是前端开发中优化网页性能的重要策略。让我们先解释一下这两个概念以及它们的区别:
    1. 重排(reflow):当浏览器需要重新计算元素的几何尺寸和位置时,会发生重排。这通常发生在文档结构发生变化时,例如添加或删除可见的DOM元素,或者更改元素的尺寸、边距、填充等。重排是一个成本较高的操作,因为它涉及到了布局计算。

    2. 重绘(repaint):当元素的颜色或其他不影响其几何形状的样式发生改变时,就会发生重绘。这通常比重排要快,因为不需要重新计算布局。

    样式属性指的是通过JavaScript修改元素的style属性,例如element.style.width = '200px'。这可以直接更改CSS的内联样式。

    CSS3变换是指使用CSS的transform属性来修改元素的位置、旋转、缩放等。例如,你可以这样写:

    .element {transform: translate(50px, 50px);
    }
    

    使用CSS3变换而不是直接改变样式属性来实现动画效果,是因为CSS3变换通常会被浏览器优化为GPU加速的操作。这意味着浏览器可以利用图形处理单元(GPU)来处理这些变化,而不会频繁地触发重排或重绘,从而提高性能。

    举个例子,如果你用JavaScript不断修改一个元素的位置(比如element.style.left),每次修改都可能触发重排或重绘。相反,如果你使用CSS3变换来移动元素,比如使用transform: translate(),那么这个操作更有可能只触发一次重绘,并且可能会被浏览器优化为GPU上的操作,从而减少对性能的影响。

    总结来说,尽管样式属性也可以用来实现动画效果,但是使用CSS3变换可以更好地利用硬件加速,减少不必要的重排和重绘,从而提升页面的渲染性能。

  • 异步加载资源:使用asyncdefer属性来异步加载JavaScript文件,避免阻塞页面渲染。

    • defer 属性是HTML <script> 标签的一个属性,用于指定脚本应该在文档解析完成后,但在 DOMContentLoaded 事件触发之前执行。这个属性可以帮助你在不阻塞页面渲染的情况下加载和执行脚本。
    defer 属性的作用
    1. 避免阻塞页面渲染:默认情况下,浏览器会按照脚本在文档中的位置顺序执行脚本。如果脚本位于 <head> 中或页面内容之前,那么它可能会阻塞页面的渲染。使用 defer 属性可以让脚本在文档解析完成之后执行,从而避免阻塞页面的渲染。
    2. 保持脚本执行顺序:即使脚本是异步加载的,defer 也保证了脚本按照它们在HTML文档中出现的顺序执行。这对于依赖于顺序执行的脚本来说非常重要。
    何时使用 defer
    • 当脚本依赖于文档结构时:如果脚本需要访问文档中的元素,那么最好使用 defer,因为脚本会在文档完全解析之后执行,这时文档中的所有元素都已经加载完毕。
    • 当脚本需要按顺序执行时:如果脚本之间存在依赖关系,并且需要按照一定的顺序执行,那么使用 defer 可以确保脚本按顺序执行。
    defer 与 async 的区别
    • 执行时机

      • async:脚本在下载完成后立即执行,不论文档解析是否完成。这意味着多个带有 async 属性的脚本可能会并行执行,并且执行顺序不可预测。
      • defer:脚本在文档解析完成后执行,但在 DOMContentLoaded 事件触发之前。这意味着多个带有 defer 属性的脚本会按照它们在HTML文档中的顺序依次执行。
    • 加载时机

      • async:脚本在下载时不会阻塞文档的解析,脚本可能在文档解析过程中加载完成并执行。
      • defer:脚本在文档解析过程中下载,但不会阻塞文档的解析。脚本在文档解析完成后执行。

    示例

    假设你有两个脚本 script1.jsscript2.jsscript2.js 需要依赖 script1.js 的执行结果。

    使用 defer
    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><title>Document</title>
    </head>
    <body><!-- 页面内容 --><script src="script1.js" defer></script><script src="script2.js" defer></script>
    </body>
    </html>
    

    在这个例子中,script1.jsscript2.js 都会异步下载,但它们会在文档解析完成后按顺序执行。

    使用 async
    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><title>Document</title>
    </head>
    <body><!-- 页面内容 --><script src="script1.js" async></script><script src="script2.js" async></script>
    </body>
    </html>
    

    在这个例子中,script1.jsscript2.js 会并行下载并执行,执行顺序不可预测。

    总结
    • defer:适用于需要按顺序执行的脚本,且脚本执行依赖于文档解析完成的情况。
    • async:适用于可以并行加载和执行的脚本,且脚本执行顺序不重要的情况。

    通过合理使用 deferasync 属性,你可以优化页面加载速度和脚本执行顺序,从而提升用户体验。

  • 使用requestAnimationFrame:在实现动画时使用requestAnimationFrame代替setTimeoutsetInterval,以获得更好的性能。

    • requestAnimationFrame (raf) 是一种用于在浏览器中创建动画的技术,相比传统的 setTimeoutsetInterval 方法,它能够提供更好的性能和用户体验。以下是为什么 raf 能够获得更好性能的一些原因:
    1. 同步与浏览器的重绘周期

    raf 调度动画帧的方式与浏览器的刷新周期同步。浏览器通常的目标刷新率是每秒 60 帧(60fps),即大约每 16.67 毫秒刷新一次屏幕。raf 会在下一帧绘制之前调度回调函数,这意味着它会在浏览器准备绘制下一帧时调用你的动画函数,而不是在任意时刻。

    2. 更高的效率

    raf 会根据浏览器的实际刷新率来安排动画帧,这意味着即使在刷新率低于 60fps 的情况下(例如在设备负载较高的时候),raf 也能调整其回调的时间间隔,以适应当前的刷新率。这样可以避免不必要的计算和重绘,提高效率。

    3. 暂停动画

    当浏览器标签页失去焦点时(例如用户切换到另一个标签页),raf 会暂停动画,因为在这种情况下,渲染动画是没有意义的。相反,setTimeoutsetInterval 会继续执行它们的回调,即使用户看不到结果,这也浪费了计算资源。

    4. 更好的性能表现

    由于 raf 的回调是在浏览器绘制下一帧之前被调用的,因此可以确保动画与重绘同步,从而避免闪烁和其他视觉上的不连贯现象。这使得动画更加流畅和平滑。

    5. 灵活性

    raf 提供了一种灵活的方式来控制动画的每一帧。你可以根据需要在每一帧中调整动画状态,而 setTimeoutsetInterval 通常需要开发者自己管理定时和状态更新。

    示例比较
    使用 setInterval 实现动画
    let position = 0;
    const intervalId = setInterval(() => {position += 10;document.getElementById('box').style.left = `${position}px`;if (position >= 200) clearInterval(intervalId);
    }, 50);  // 每 50 毫秒移动一次
    
    使用 requestAnimationFrame 实现动画
    let position = 0;
    const animate = () => {position += 10;document.getElementById('box').style.left = `${position}px`;if (position < 200) {requestAnimationFrame(animate);}
    };
    requestAnimationFrame(animate);
    
    总结

    使用 requestAnimationFrame 能够确保动画与浏览器的刷新周期同步,这样可以避免不必要的计算,节省计算资源,并且提供更加平滑和一致的动画体验。因此,在实现动画时推荐使用 raf 替代 setTimeoutsetInterval

6. 代码分割
  • 按需加载:使用Webpack等构建工具将代码拆分成多个小块,按需加载。
  • 动态导入:使用import()语法来实现模块的动态导入,进一步减少初次加载时的文件大小。
7. 优化JavaScript执行
8. 监控性能
  • 使用Lighthouse:Lighthouse是Google提供的性能审计工具,可以帮助你评估网站的性能并给出改进建议。
  • 持续集成/持续部署 (CI/CD):在构建流程中加入性能测试,确保每次部署都不会影响性能。
9. 用户体验优化

五、结论

虽然前端开发中直接管理内存的机会较少,但理解内存的工作原理和采取有效的预防措施,可以显著提升应用的性能和用户体验。通过避免内存泄漏、优化资源加载和使用,以及持续监控性能,可以确保前端应用始终保持高效和响应迅速。


http://www.ppmy.cn/embedded/108098.html

相关文章

Python 在Excel中应用和取消多种不同类型的数据筛选

目录 安装Python Excel处理库 Python 在 Excel 中应用文本筛选 Python 在 Excel 中应用数字筛选 Python 在 Excel 中应用字体颜色、单元格颜色或图标集筛选 Python 在 Excel 中应用日期筛选 Python 在 Excel 中应用动态日期筛选 Python 在 Excel 中筛选空单元格或非空单…

在 Go 语言中使用模块

模块很重要,因为它们允许将相关的代码文件组织到同一个包中,并以一种提高简单性和可重复性的方式组织代码。 1. 开始使用模块 从代码的角度看,模块是 Go 包和文件以及名为 go.mod 的文件的集合。在接下来的步骤中,将学习如何创建模块,然后使用它。 2. 第一步:创建项目目…

【最新华为OD机试E卷-支持在线评测】机器人活动区域(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,…

百家云 BRTC:革新华为 HarmonyOS NEXT 系统的实时通信体验

随着技术的不断进步&#xff0c;实时通信已成为企业和个人用户日常互动不可或缺的一部分。百家云BRTC&#xff0c;作为一款领先的实时通信PaaS云服务&#xff0c;近日宣布了一项重大更新——专为华为HarmonyOS NEXT系统打造的全新SDK。这一创新举措不仅标志着BRTC在音视频技术领…

【计算机网络】浏览器输入访问某网址时,后台流程是什么

在访问网址时&#xff0c;后台的具体流程可以因不同的网站、服务器和应用架构而异。 实际过程中可能还涉及更多的细节和步骤&#xff0c;如缓存处理、重定向、负载均衡等。 此外&#xff0c;不同的网站和应用架构可能会有不同的实现方式和优化策略。 部分特定网站或应用&#x…

什么是IP宿主信息?

IP宿主信息是指通过IP地址与POI&#xff08;Point of Interest&#xff0c;兴趣点&#xff09;/AOI&#xff08;Area of Interest&#xff0c;兴趣区域&#xff09;信息关联&#xff0c;进而整理分析Whois数据所得到的一系列信息。这些信息主要包括互联网服务提供商&#xff08…

[Go]-抢购类业务方案

文章目录 要点&#xff1a;1. 抢购/秒杀业务的关键挑战2. 技术方案3.关键实现点4.性能优化建议5.其他考虑因素 细节拆分&#xff1a;1. **高并发处理**2.**限流与防护**3.**库存控制**4. **异步处理**5. **数据一致性**6. **常用架构设计**7. **代码示例**8. 进一步优化9. 注意…

828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器

一、前言 这篇文章讲解&#xff1a; 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器&#xff0c;完成视频直播需求。 随着实时视频流传输需求的增长&#xff0c;RTSP&#xff08;实时流协议&#xff09;服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当…