# lagou-fed-javascript-performance-optimization **Repository Path**: showlotus/lagou-fed-javascript-performance-optimization ## Basic Information - **Project Name**: lagou-fed-javascript-performance-optimization - **Description**: 拉勾教育-大前端-JavaScript性能优化学习笔记 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-06-06 - **Last Updated**: 2023-06-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1. JavaScript 性能优化 ## 1.1. 内存管理 ### 1.1.1. 介绍 - 内存:由可读可写单元组成,表示一片可操作空间 - 管理:人为的去操作一片空间的申请、使用和释放 - 内存管理:开发者主动去申请空间、使用空间、释放空间 - 管理流程:申请——使用——释放 ### 1.1.2. JavaScript 中的内存管理 - 申请内存空间 - 使用内存空间 - 释放内存空间 [示例代码](./src/02-javascript-memory-manage.js) ## 1.2. 垃圾回收与常见 GC 算法 ### 1.2.1. JavaScript 中的垃圾回收 - JavaScript 中内存管理是自动的 - 对象不再被**引用**时是垃圾 - 对象不能**从根上访问**到时是垃圾 JavaScript 引擎把垃圾占据的对象空间进行回收,这一过程就被成为 JavaScript 的垃圾回收。 可达对象: - 可以访问到的对象就是可达对象(引用、作用域连) - 可达的标准就是从根出发是否能够被找到 - JavaScript 中的根就可以理解为全局对象 [引用代码示例](./src/03-reference.js) [可达代码示例](./src/04-reachable-obj.js) ![可达对象图示](./src/images/04-reachable-obj.png) ### 1.2.2. GC 算法 #### 1.2.2.1. 介绍 - GC 定义与作用: - GC 就是垃圾回收机制的简写 - GC 可以找到内存中的垃圾、并释放和回收空间 - GC 中的垃圾: - 程序中不再需要使用的对象 ```js function func() { // 从需求角度来看func函数调用执行后可能就不再需要name,应该被当作垃圾回收 name = "lg"; return `${name} is a coder`; } func(); ``` - 程序中不能再访问到的对象 ```js function func() { // func函数调用执行后,函数外部无法访问变量name const name = "lg"; return `${name} is a coder`; } func(); ``` - GC 算法: - GC 是一种机制,垃圾回收器完成具体的工作 - 工作的内容就是查找垃圾、释放空间和回收空间 - 算法就是工作时查找和回收所遵循的规则 - 常见 GC 算法 - 引用计数 - 标记清除 - 标记整理 - 分代回收 #### 1.2.2.2. 引用计数算法 #### 1.2.2.3. 标记清除算法 #### 1.2.2.4. 标记整理算法 - 标记整理可以看作是标记清除的增强 - 标记阶段的操作和标记清除一样 - 清除阶段会先执行整理,移动对象位置 ![回收前](./src/images/09-before.png) ![整理后](./src/images/09-ing.png) ![回收后](./src/images/09-after.png) #### 1.2.2.5. 常见 GC 算法总结 - 引用计数 - 优点: - 可以即时回收垃圾对象 - 减少程序卡顿时间 - 缺点: - 无法回收循环引用的对象 - 资源消耗较大 - 标记清除 - 优点: - 可以回收循环引用的对象 - 缺点: - 容易产生碎片化空间,浪费空间 - 不会立即回收垃圾对象 - 标记整理 - 优点: - 减少碎片化空间 - 缺点: - 不会立即回收垃圾对象 ## 1.3. V8 引擎的垃圾回收 ### 1.3.1. 认识 V8 - V8 是一款主流的 JavaScript 执行引擎 - V8 采用即时编译 - V8 内存设限 - 64 位操作系统不超过 1.5GB - 32 位操作系统不超过 800MB ### 1.3.2. V8 垃圾回收策略 - 采用分代回收的思想 - 内存分为新生代、老生代 - 针对不同对象采用不同算法 - V8 中常用 GC 算法 - 分代回收 - 空间复制 - 标记清除 - 标记整理 - 标记增量 ![V8 垃圾回收策略图示](./src/images/12-v8-gc.png) ### 1.3.3. V8 如何回收新生代对象 #### 1.3.3.1. V8 内存分配 - V8 内存空间一分为二 - 小空间用于存储新生代对象(32M | 16M) - 新生代指的是存活时间较短的对象 #### 1.3.3.2. 新生代对象回收实现 - 回收过程采用复制算法+标记整理 - 新生代内存区分为两个等大小空间 - 使用空间为 From,空闲空间为 To - 活动对象存储于 From 空间 - 标记整理后将活动对象拷贝至 To - From 与 To 交换空间完成释放 #### 1.3.3.3. 回收细节说明 - 拷贝过程中可能出现晋升 - 晋升就是将新生代对象移动至老生代 - 一轮 GC 还存活的新生代需要晋升 - To 空间的使用率超过 25% ### 1.3.4. V8 如何回收老生代对象 #### 1.3.4.1. 老生代对象说明 - 老生代对象存放在右侧老生代区域 - 64 位操作系统 1.4G,32 位操作系统 700M - 老生代对象就是指存活时间较长的对象 #### 1.3.4.2. 老生代对象回收实现 - 主要采用标记清除、标记整理、增量标记算法 - 首先使用标记清除完成垃圾空间的回收 - 采用标记整理进行空间优化 - 采用增量标记进行效率优化 #### 1.3.4.3. 细节对比 - 新生代区域垃圾回收使用空间换时间 - 老生代区域垃圾回收不适合复制算法 ![标记增量如何优化垃圾回收](./src/images/14-v8-gc.png) ### 1.3.5. 总结 - V8 是一款主流的 JavaScript 执行引擎 - V8 内存设置上限 - V8 采用基于分代回收思想实现垃圾回收 - V8 内存分为新生代和老生代 - V8 垃圾回收常见的 GC 算法 ## 1.4. Performance 工具 ### 1.4.1. Performance 工具介绍 #### 1.4.1.1. 为什么使用 Performance - GC 的目的是为了实现内存空间的良性循环 - 良性循环的基石是合理使用 - 即刻关注才能确定是否合理 - Performance 提供多种监控方式 **通过 Performance 时刻监控内存** #### 1.4.1.2. Performance 使用步骤 - 打开浏览器输入目标网址 - 进入开发人员工具面板,选择性能 - 开启录制功能,访问具体界面 - 执行用户行为,一段时间后停止录制 - 分析界面中记录的内存信息 ### 1.4.2. 内存问题的体现 - 页面出现延迟加载或经常性暂停(频繁垃圾回收) - 页面持续性出现糟糕的性能(内存膨胀) - 页面的性能随时间延长越来越差(内存泄露) ### 1.4.3. 监控内存的几种方式 - 界定内存问题的标准 - 内存泄露:内存使用持续升高 - 内存膨胀:在多数设备上都存在性能问题 - 频繁垃圾回收:通过内存变化图进行分析 - 监控内存方式: - 浏览器任务管理器 - Timeline 时序图记录 - 堆快照查找分离 DOM - 判断是否存在频繁的垃圾回收 ### 1.4.4. 任务管理器监控内存 - 内存:表示原生内存,当前 DOM 节点所占据的内存 - JavaScript 内存:表示 JS 堆,小括号中的值代表当前可达对象正在使用的内存大小。 [示例代码](./src/06-task-manager.html) ### 1.4.5. Timeline 记录内存 [示例代码](./src/07-timeline.html) ### 1.4.6. 堆快照查找分离 DOM - 什么是分离 DOM - 界面元素存活在 DOM 树上 - 垃圾对象时的 DOM 节点 - 分离状态的 DOM 节点 [示例代码](./src/08-heap.html) ### 1.4.7. 判断是否存在频繁 GC - 为什么 - GC 工作时应用程序停止 - 频繁且过长的 GC 会导致应用假死 - 用户使用中感知应用卡顿 - 如何确定 - Timeline 中频繁的上升下降 - 任务管理器中数据频繁的增加减少 ### 1.4.8. 总结 - Performance 使用流程 - 内存问题的相关分析 - Performance 时序图监控内存变化 - 任务管理器监控内存变化 - 堆快照查找分离 DOM ## 1.5. 代码优化实例 ### 1.5.1. 介绍 - 如何精准测试 JavaScript 性能 - 本质上就是采用大量的执行样本进行数学统计和分析 - 使用基于 Benchmark.js 的[https://jsperf.com/](https://jsperf.com/)完成 - Jsperf 使用流程 - 使用 Github 账号登录 - 填写个人信息(非必须) - 填写详细的测试用例信息(title、slug) - 填写准备代码(DOM 操作时经常使用) - 填写必要有 setup 与 teardown 代码 - 填写测试代码片段 ### 1.5.2. 慎用全局变量 - 为什么要慎用 - 全局变量定义在全局执行上下文,是所有作用域链的顶端 - 全局执行上下文一直存在上下文执行栈,直到程序退出 - 如果某个局部作用域出现了同名变量则会遮蔽或污染全局 ### 1.5.3. 缓存全局变量 将使用中无法避免的全局变量缓存到局部 [示例代码](./src/09-variable-cache.html) ### 1.5.4. 通过原型对象添加附加方法 在原型对象上新增实例对象需要的方法 [示例代码](./src/10-func-constructor.js) ### 1.5.5. 避开闭包陷阱 - 闭包特点 - 外部具有指向内部的引用 - 在“外部”作用域访问“内部”作用域的数据 ```js function foo() { var name = "lg"; function fn() { console.log(name); } return fn; } var a = foo(); a(); ``` * 关于闭包 - 闭包是一种强大的语法 - 闭包使用不当很容易出现内存泄露 - 不要为了闭包而闭包 [示例代码](./src/11-closure.html) ### 1.5.6. 避免属性访问方法使用 JavaScript 中的面向对象 - JS 不需要属性的访问方法,所有属性都是外部可见的 - 使用属性访问方法只会增加一层重定义,没有访问的控制力 [示例代码](./src/12-property-access.js) ### 1.5.7. For 循环优化 [示例代码](./src/13-for.html) ### 1.5.8. 选择最优的循环方法 [示例代码](./src/13-for-forin-forEach.js) ### 1.5.9. 文档碎片优化节点添加 [示例代码](./src/14-append-node.html) ### 1.5.10. 克隆优化节点操作 [示例代码](./src/15-node-clone.html) ### 直接量替换 new Object [示例代码](./src/16-object.js)