关于前端:JavaScript-性能优化

8次阅读

共计 2495 个字符,预计需要花费 7 分钟才能阅读完成。

垃圾回收

内存治理

JavaScript 没有提供操作内存的 API,所有内存操作都是主动的。

  • 申请
  • 应用
  • 开释

何为垃圾

  • 对象不再被援用
  • 对象不能从根上拜访到

可达对象

  • 能够拜访到的对象(援用、作用域链)
  • 规范是从根登程是否能被找到
  • JavaScript 中的根能够了解为全局变量对象

GC 算法

  • GC 就是垃圾回收机制的简写
  • GC 能够找的内存中的垃圾、并开释和回收空间

    • 程序中不再须要应用的对象
    • 程序中不能在拜访到的对象
  • 算法就是工作时查找和回收所遵循的规定

援用计算算法

核心思想 :设置援用计数器,援用关系扭转时批改援用数字,当援用数字为 0 时立刻回收

长处

  • 发现垃圾立刻回收
  • 最大限度缩小程序暂停(GC 时程序是暂停的)

毛病

  • 无奈回收循环援用的对象
// 当 fn 执行结束后,obj1 和 obj2 将不在被全局援用,计数器应该为 0
// 但 obj1 和 obj2 在办法内还存在指向关系,所以计数器不为 0,无奈被回收
function fn() {const obj1 = {};
  const obj2 = {};

  obj1.name = obj2;
  obj2.name = obj1;

  return "haha";
}

fn();
  • 工夫开销大(监控保护计数器)

标记革除算法

核心思想 :分为标记和分明两个阶段。第一阶段便当所有对象标记流动对象(可达对象),第二阶段便当所有对象分明没有被标记的对象,最初回收相应的空间。

长处

  • 相比于援用计算算法,标记革除能够革除掉没有被援用的对象

毛病

  • 空间碎片化

如上图,GC 进行操作时发现只有两头对象可达进行标记,左右两边对象进行革除。在咱们下次进行内存调配时假如需要 3 个域大小的内存,但目释放出来的空间因为不间断所以只有两个,须要开拓新的内存空间,造成节约。

标记整顿算法

核心思想 :标记整顿能够看作是标记革除的加强,在标记阶段是完全一致的,在革除阶段会先执行整顿,使空间间断,再去革除。

意识 V8

  • V8 是一款支流的 JavaScript 执行引擎
  • V8 采纳即时编译
  • V8 内存设限 (64 为 1.5g / 32 位 800m)

回收策略

采纳分代回收的思维,将内存分为新生代和老生代。针对不同对象采纳不同的算法

新生代

  • 新生代指存活工夫较短的对象 (部分作用域等)。
  • 小空间用户存储新生代对象 (32M|16M)

回收过程

  • 回收过程采纳复制算法 + 标记整顿
  • 新生代将内存分为两个等大小的空间

    • 应用空间为 From,闲暇空间为 To
  • 流动对象存储在 From 空间
  • 标记整顿后将流动对象拷贝至 To
  • From 进行开释
  • From 与 To 替换空间实现开释

阐明 :一轮 GC 后还存活的新生代须要降职(将新生代对象挪动到老生代),To 空间使用率超过 25% 也须要降职

老生代

  • 搁置在右侧老生代区域 (1.4G|700G)
  • 老年代对象就是指存活工夫较长的对象(全局属性、闭包等)

回收过程

  • 次要采纳标记革除、标记整顿、增量标记算法
  • 首先应用标记革除实现垃圾空间回收
  • 采纳标记整顿进行空间优化(降职)
  • 采纳增量标记进行效率优化(GC 与程序交替进行)

比照

  • 新生代区域垃圾回收应用空间换工夫
  • 老生代区域垃圾回收不适宜复制算法,因为数据量大

性能优化

  • 缓存数据
  • 缩小拜访层级
  • 缩小判断层级
  • 缩小循环体流动

防抖与节流

  • 防抖:高频点击操作咱们能够人为管制是第一次还是最初一次
  • 节流:高频操作咱们能够本人设置频率,让原本会触发屡次的事件能够依照咱们定义的频率执行

防抖

var oBtn = document.getElementById("btn");
/**
 * handle 须要执行的事件
 * wait 事件触发多久后开始执行
 * immediate 管制是第一次执行还是最初一次
 * */
function myDebounce(handle, wait = 1000, immediate = false) {
  let timer = null;
  return function proxy() {clearTimeout(timer);
    const init = immediate && !timer;
    // immediate = false 也就是最初一次执行时触发
    timer = setTimeout(() => {
      timer = null;
      !immediate ? handle() : null;}, wait);
    // immediate = true 也就是第一次执行时触发
    // 立刻执行也须要依赖于 setTimeout 管制工夫,当 timer 为空时执行
    init ? handle() : null;};
}

function btnClick() {console.log("点击了");
}

oBtn.onclick = myDebounce(btnClick, 500, true);

节流

function scrollFn() {console.log("滚动了");
}
/**
 * wait 频率
 * now 当初工夫
 * pervious 上次执行工夫
 * wait - (now - previous)
 * 如上述计算结果是大于 0 的,就意味着以后操作是一个高频的,咱们就要想方法让他不去执行 handle
 * 如果小于 0,那就是一个非高频的,那么就能够间接触发 handler
 * */
function myThrottle(handle, wait = 1000) {
  let previous = 0; // 上一次执行工夫
  let timer = null;
  return function proxy() {let now = new Date(); // 以后次执行工夫
    let interval = wait - (now - previous);
    if (interval <= 0) {
      // 非高频次能够立刻执行
      // 避免浏览器监听触发的事件和咱们节流的工夫重合,只走第一个 if
      clearTimeout(timer);
      timer = null;
      handle();
      previous = new Date();} else if (!timer) {
      // 当零碎中曾经有一个定时器再期待,那么就不须要再次开启一个定时器
      // 高频次,期待 interval 执行
      timer = setTimeout(() => {clearTimeout(timer); // 是革除了定时器,但 timer 值还在
        timer = null;
        handle();
        previous = new Date();}, interval);
    }
  };
}

window.onscroll = myThrottle(scrollFn, 2000);
正文完
 0