• JavaScript 性能优化

      • 内存管理
        • 内存:由可读写单元组成,表示一片可操作空间
        • 管理:人为的去操作一片空间的申请、使用和释放
        • 内存管理: 开发者主动申请空间、使用空间、释放空间
        • 管理流程:
          • 申请 let obj = {}
          • 使用 obj.name = ‘lg’
          • 释放 obj = null

    • 垃圾回收
      • JavaScript中内存管理是自动
      • 对象不再被引用时是垃圾
      • 对象不能从根上访问到时是垃圾
      • 可达对象: 可以访问到的对象(引用、作用域链)
      • 可达的标准就是从根出发是否能够被找到
      • JavaScript中的根就可以理解为是全局变量对象
    1. let obj = {name: 'xm'}
    2. let ali = obj; //xm对象被两个引用
    3. obj = null //obj没有引用xm了,只有ali了,xm对象仍是可达对象

    • GC 算法
      • GC 就是垃圾回收机制的简写,垃圾回收器完成具体的工作
      • GC可以找到内存中的垃圾、并释放和回收空间
      • 算法就是工作时查找和回收所遵循的规则

    • 常用GC算法
      • 引用计数
        • 发现垃圾时立即回收
        • 最大限度减少程序暂停
        • 缺点: 无法回收循环引用的对象
        • 时间开销大
    1. - 标记清除
    2. - 核心思想:分标记和清除二个阶段完成
    3. - 遍历所有对象找标记活动对象
    4. - 遍历所有对象清除没有标记对象
    5. - 回收相应的空间
    6. - 优点:能解决循环引用不能回收的问题
    7. - 缺点:会产生空间碎片化问题
    8. - 标记整理
    9. - 标记整理可以看做是标记清除的增强
    10. - 标记阶段的操作和标记清除一致
    11. - 清除阶段会先执行整理,移动对象位置
    12. - 分代回收

    • V8
      • V8是一款主流的JavaScript执行引擎
      • V8采用即时编译
      • V8内存设限

    • V8垃圾回收策略
      • 采用分代回收的思想
      • 内存分为新生代、老生代
      • 针对不同对象采用不同算法

    • V8中常用GC算法
      • 分代回收
      • 空间复制
      • 标记清除
      • 标记整理
      • 标记增量

    • Performance工具介绍
      • GC的目的是为了实现内存空间的良性循环
      • 良性循环的基石是合理使用
      • 时间关注才能确定是否合理
      • Performance提供多种监控方式
      • 通过Performance时刻监控内存

    • Performance使用步骤
      • 打开浏览器输入目标网址
      • 进入开发人员工具面板,选择性能
      • 开启录制功能,访问具体界面
      • 执行用户行为,一段时间后停止录制
      • 分析界面中记录的内存信息

    • 内存问题的外在表现
      • 页面出现延迟加载或经常必暂停
      • 页面持续性出现糟糕的性能
      • 页面的性能随时间延长越来越差

    • 界定内存问题的标准
      • 内存泄露:内存使用持续升高
      • 内存膨胀:在多数设备上都存在性能问题
      • 频繁垃圾回收:通过内存变化图进行分析

    • 监控内存的几种方式
      • 浏览器任务管理器
      • Timeline时序图记录
      • 堆快照查找分离DOM
      • 判断是否存在频繁的垃圾回收

    • 任务管理器监控内存
      • Shift + Esc打开浏览器任务管理器
      • 右键选中JavaScript使用的内存
      • 内存列:当前DOM节点所占内存,如果频繁在变,说明当前界面存在频繁的DOM操作
      • JavaScript内存列: 小括号里的内存如果一直在增加,说明内存一直往上走,有问题

    • Timeline记录内存
      • 打开F12,然后Performance
      • 点击录屏,操作页面之后,点结束
      • 然后查看数据Screenshots,Memory
      • 查看蓝色波浪线,如果一直上升没下降的,鼠标移上去找到对应画面即代码块

    • 堆快照查找分离DOM
      • 打开F12,然后Memory
      • 在操作前,点击Take snapsshot,保存操作前快照
      • 找到要查找的对象值后,回到Profiles
      • 操作之后,重新拍照
      • 之前在代码中创建的DOM节点,这时候就能查得到了
      • 在确定一个DOM元素在后续不会使用之后,可以在代码中置为null

    • 为什么要确定频繁垃圾回收
      • GC工作时应用程序是停止的
      • 频繁且过长的GC会导致应用假死
      • 用户使用中感知应用卡顿
      • 判断方法一:Timeline中频繁的上升下降
      • 二:任务管理器中数据频繁的增加减小

    • 优化代码,如何精准测试JavaScript性能

    • 为什么要慎用全局变量
      • 全局变量定义在全局执行上下文,是所有作用域链的顶端
      • 全局执行上下文一直存在于上下文执行栈,直到程序退出
      • 如果某个局部作用域出现了同名变量则会遮蔽或污染全局
    1. var i, str = ''
    2. for (i = 0; i < 1000; i++) {
    3. str += i
    4. }
    5. for (let i = 0; i < 1000; i++) {
    6. let str = ''
    7. str += i
    8. }

    示例图:
    jsperf性能对比.png


    • 缓存全局变量
      • 将使用中无法避免的全局变量缓存到局部
    1. function getBtn2() {
    2. let obj = document //缓存document
    3. let oBtn1 = obj.getElementById('btn1')
    4. let oBtn3 = obj.getElementById('btn3')
    5. let oBtn5 = obj.getElementById('btn5')
    6. let oBtn7 = obj.getElementById('btn7')
    7. let oBtn9 = obj.getElementById('btn9')
    8. }

    • 通过原型对象添加附加方法
      ```javascript var fn1 = function() {

      1. this.foo = function() {
      2. console.log(11111)
      3. }

      }

      let f1 = new fn1()

    1. var fn2 = function() {}
    2. fn2.prototype.foo = function() { //通过构造函数原型对象通过prototype直接指向foo,设置函数
    3. console.log(11111)
    4. }
    5. let f2 = new fn2() //接收拿到的实例对象
    1. - <br />避开闭包陷阱
    2. - 特点:外部具有指向内部的引用
    3. - 在“外”部作用域访问“内”部作用域的数据
    4. - 闭包是一种强大的语法
    5. - 闭包使用不当很容易出现内存泄露
    6. - 不要为了闭包而闭包
    7. ```javascript
    8. function foo() {
    9. var el = document.getElementById('btn');
    10. el.onclick = function() {
    11. console.log(el.id)
    12. }
    13. el = null; //优化,当btnDOM元素被删除后,有了这个null,el及btn这一块空间将得到释放
    14. }
    15. foo();

    • 避免属性访问方法使用
      • Javascript中的面向对象
        • JS不需属性的访问方法,所有属性都是外部可见的
        • 使用属性访问方法只会增加一层重定义,没有访问的控制力
    1. function Person() {
    2. this.name = 'icoder'
    3. this.age = 18
    4. this.getAge = function() {
    5. return this.age
    6. }
    7. }
    8. const p1 = new Person()
    9. const a = p1.getAge()
    10. function Person() {
    11. this.name = 'icoder'
    12. this.age = 18
    13. }
    14. const p2 = new Person()
    15. const b = p2.age

    • For循环优化

      1. var arrList = []
      2. arrList[10000] = 'icoder'
      3. for (var i = 0; i < arrList.length; i++) {
      4. console.log(arrList[i])
      5. }
      6. for (var i = arrList.length; i; i--) {
      7. console.log(arrList[i])
      8. }
      9. for (var i = 0,len = arrList.length; i < len; i++) {
      10. console.log(arrList[i])
      11. }
      12. //三种比较
      13. var arrList = new Array(1, 2, 3, 4, 5)
      14. arrList.forEach(function(item) { //最优
      15. console.log(item)
      16. })
      17. for (var i = arrList.length; i; i--) { //次之
      18. console.log(arrList[i])
      19. }
      20. for (var i in arrList) { //最差
      21. console.log(arrList[i])
      22. }

    • 文档碎片优化节点添加

      • 节点的添加操作必然会有回流和重绘
    1. for (var i = 0; i < 10; i++) {
    2. var oP = document.createElement('p')
    3. oP.innerHTML = i
    4. document.body.appendChild(oP)
    5. }
    6. const fragEle = document.createDocumentFragment()
    7. for (var i = 0; i < 10; i++) {
    8. var oP = document.createElement('p')
    9. oP.innerHTML = i
    10. fragEle.appendChild(oP)
    11. }
    12. document.body.appendChild(fragEle)

    • 克隆来优化节点操作
      • cloneNode() 方法可创建指定的节点的精确拷贝。
      • cloneNode() 方法 拷贝所有属性和值。
      • 该方法将复制并返回调用它的节点的副本。如果传递给它的参数是 true,它还将递归复制当前节点的所有子孙节点。否则,它只复制当前节点。
    1. <body>
    2. <p id="box1">old</p>
    3. <script>
    4. for (var i = 0; i < 3; i++) {
    5. var oP = document.createElement('p')
    6. oP.innerHTML = i
    7. document.body.appendChild(oP)
    8. }
    9. var oldP = document.getElementById('box1')
    10. for (var i = 0; i < 3; i++) {
    11. var newP = oldP.cloneNode(false)
    12. newP.innerHTML = i
    13. document.body.appendChild(newP)
    14. }
    15. </script>
    16. </body>

    • 直接量替换new Object

      1. var a = [1, 2, 3] //直接量,优
      2. var a1 = new Array(3) //new Object,差
      3. a1[0] = 1
      4. a1[1] = 2
      5. a1[2] = 3

    • JS性能优化二


      • jsperf.com不能用了,用jsbench.me

      • 堆栈中的JS执行过程
        • JS代码在开始执行后,首先会在堆内存里创建一个执行环境栈ECStack
        • 执行环境栈ECStack存储不同的执行上下文
        • 从上往下创建执行上下文
        • 基本类型数值都是存放在栈里面,一般主线程管理
        • 引用类型存放在堆里面,一般用GC处理
        • 在栈内存里,每遇到一个新函数,都会重新生成一个执行上下文进栈

    示例图:


    JS执行过程.png


    • 减少判断层级
      • 每当碰到多层if else 判断时,考虑下提前return的操作,以减少层级
      • 还可以考虑switch case
    1. function doSomething (part, chapter) {
    2. const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
    3. if (part) {
    4. if (parts.includes(part)) {
    5. console.log('属于当前课程')
    6. if (chapter > 5) {
    7. console.log('您需要提供 VIP 身份')
    8. }
    9. }
    10. } else {
    11. console.log('请确认模块信息')
    12. }
    13. }
    14. doSomething('ES2016', 6)
    15. //优
    16. function doSomething (part, chapter) {
    17. const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
    18. if (!part) {
    19. console.log('请确认模块信息')
    20. return
    21. }
    22. if (!parts.includes(part)) return
    23. console.log('属于当前课程')
    24. if (chapter > 5) {
    25. console.log('您需要提供 VIP 身份')
    26. }
    27. }
    28. doSomething('ES2016', 6)

    • 减少作用域链查找层级
      ```javascript var name = ‘zce’

      function foo () { name = ‘zce666’ // 这里的Name 是属于全局的 function baz () { var age = 38 console.log(age) console.log(name) } baz() }

      foo()

    //优 var name = ‘zce’

    function foo () { var name = ‘zce666’ // 这里的Name 是属于全局的,但这里是牺牲空间获取速度,如果name数据量大,那上面那种方式可能更合适 function baz () { var age = 38 console.log(age) console.log(name) } baz() }

    foo()

    1. - <br />减少数据读取次数<br />
    2. ```javascript
    3. <body>
    4. <div id="skip" class="skip"></div>
    5. <script>
    6. var oBox = document.getElementById('skip')
    7. function hasEle (ele, cls) {
    8. return ele.className == cls
    9. }
    10. function hasEle (ele, cls) {
    11. var clsname = ele.className //频繁要读取的数据进行了缓存 优
    12. return clsname == cls
    13. }
    14. console.log(hasEle(oBox, 'skip'))
    15. </script>
    16. </body>
    • 字面量与构造式
    1. //构造式 慢
    2. let test = () => {
    3. let obj = new Object()
    4. obj.name = 'zce'
    5. obj.age = 38
    6. obj.slogan = '我为前端而活'
    7. return obj
    8. }
    9. //字面量 快
    10. let test = () => {
    11. let obj = {
    12. name: 'zce',
    13. age: 38,
    14. slogan : '我为前端而活'
    15. }
    16. return obj
    17. }
    18. console.log(test())
    19. var str1 = 'zce说我为前端而活' //快 zce说我为前端而活
    20. var str2 = new String('zce说我为前端而活') //慢 [String: 'zce说我为前端而活']
    21. console.log(str1)
    22. console.log(str2)
    • 减少循环体中的活动
    1. //最次
    2. // var test = () => {
    3. // var i
    4. // var arr = ['zce', 38, '我为前端而活']
    5. // for(i=0; i<arr.length; i++) {
    6. // console.log(arr[i])
    7. // }
    8. // }
    9. //次优
    10. var test = () => {
    11. var i
    12. var arr = ['zce', 38, '我为前端而活']
    13. var len = arr.length
    14. for(i=0; i<len; i++) {
    15. console.log(arr[i])
    16. }
    17. }
    18. //最优
    19. var test = () => {
    20. var arr = ['zce', 38, '我为前端而活']
    21. var len = arr.length
    22. while(len--) {
    23. console.log(arr[len])
    24. }
    25. }
    26. test()

    • 减少声明及语句数

      1. <body>
      2. <div id="box" style="width: 100px; height: 100px;"></div>
      3. <script>
      4. // var oBox = document.getElementById('box')
      5. //对于不会每次都会使用的变量,就不用做缓存,反而会消耗时间
      6. // var test = (ele) => {
      7. // let w = ele.offsetWidth
      8. // let h = ele.offsetHeight
      9. // return w * h
      10. // }
      11. // var test = (ele) => {
      12. // return ele.offsetWidth * ele.offsetHeight
      13. // }
      14. // console.log(test(oBox))
      15. var test = () => {
      16. var name = 'zce'
      17. var age = 38
      18. var slogan = '我为前端而活'
      19. return name + age + slogan
      20. }
      21. var test = () => {
      22. //减少语句数
      23. var name = 'zce',
      24. age = 38,
      25. slogan = '我为前端而活'
      26. return name + age + slogan
      27. }
      28. console.log(test())
      29. </script>
      30. </body>

    示例图:
    jsbench.me.png

    • 采用事件绑定(委托)
    1. <body>
    2. <ul id="ul">
    3. <li>ZCE</li>
    4. <li>28</li>
    5. <li>我为前端而活</li>
    6. </ul>
    7. <script>
    8. var list = document.querySelectorAll('li')
    9. // function showTxt (ev) {
    10. // console.log(ev.target.innerHTML)
    11. // }
    12. // for(let item of list) {
    13. // item.onclick = showTxt
    14. // }
    15. //事件委托,优
    16. var oUl = document.getElementById('ul')
    17. oUl.addEventListener('click', showTxt, true)
    18. function showTxt (ev) {
    19. var obj = ev.target
    20. if (obj.nodeName.toLowerCase() === 'li') {
    21. console.log(obj.innerHTML)
    22. }
    23. }
    24. </script>
    25. </body>