JS 性能优化

144 阅读4分钟

主讲:云隐

一、回到那道面试题

面试题: 从 url 输入到页面展示发生了什么?

从输入开始

输入的是 url

  • URLurl - 资源定位符

  • url 的三种形式:

    • http 开头:http://www.zhaowa.comhttp 协议;

      • 追问:httpTCP

        • http应用层 的网络协议,TCP传输层 的协议;

        • 关联:http 是基于 TCP 实现连接,http 在发起请求之前,首先会建立一个对接服务器的通道,并且发送请求,发送请求结束之后会断开连接,其中的 建立、发送、断开TCP 连接;

          • http 是如何实现连接的?http 在发送请求的时候,是依赖 TCP 建立起来的连接通道,并且发送请求,请求完成之后会断开这个连接;

          • 优化点: 首先要知道 http 的版本:1.0、1.1、2.0

            1. UDP vs TCP 区别:UDP 是保障速度,面向连接的,TCP 是面向确认的;

            2. http1.1 版本开始增加了 keep-alive,作用是 保持 TCP 的连续畅通,不用反复地建立连接

              • 1.1 版本中,如果同时发送 10 条请求,在浏览器的瀑布流中可以观察到,一次性只会同时并发 6 条,如果其中有一条结束了,会立即补充一条,直到全部请求完成,说明了 1.1 版本中不会复用 TCP 通路,浏览器同时并发请求几条,就会建立几条 TCP 连接,而 Chrome 浏览器限定了最大的并发数量,就是 6 条;
            3. 1.12.0 版本的区别:2.0 多条并发请求复用同一条通路(复用通路,无并发限制);

              • 2.0 版本中,如果同时发送 10 条请求,那么这 10 条的请求的开始时间是一致的;
        • 差异点:http 是无状态的连接,TCP 是有状态的;

          • 优化点: 使用 socket 连接,socket 本质上不是一个协议,是封装化的 TCP。让我们的应用,更加方便地使用调用
    • https 开头:https://www.zhaowa.comhttps 协议;

      • 追问:httphttps
        • https 是什么:http + SSl(TLS),位于 TCP 协议与各种应用层协议之间;
        • 实现原理:原理图;
        • https 多次连接:1、导致网络请求加载时间延长;2、增加开销和功耗;
          • 优化点: 合并请求、长连接
            • 使用中间层(node 层)处理合并请求,或者在前端代码层把相同类型的请求合并起来;
            • 注意点:中间层在整合请求时,对于异常的处理怎么解决;
    • file 开头:file:///C:/Users/class/zhaowa/document,本地文件目录地址,只在本机打开;

HTTPS.png

域名解析

  • 域名解析的顺序:

    1. 浏览器缓存中:浏览器中会缓存 DNS 一段时间;
    2. 系统缓存:系统中找缓存,系统中没有就去找 HOST 文件;
    3. 路由器缓存:各级路由器缓存域名信息;
    4. 运营商地方站点的缓存信息【到这里,基本上就可以找到了】;
      • CDN 存在的阶段,CDN 服务提供商(partner)会在这个阶段部署很多 CDN 节点;
    5. 根域名服务器;
  • 优化:CDN - Content Delivery Network

    1. 为同一个主机配置多个 IP 地址;
    2. LB - 负载均衡;

当聊到 CDN 的时候,大多数时候会被问到缓存:缓存 => 各级缓存 => 浏览器区分缓存(协商缓存和强缓存)

web 服务器

常用服务器:apachengnix 等等;

  • 接收请求 => 传递给服务端代码;

  • 通过反向代理 => 传递给其他服务器;

  • 不同域名 => 指向相同 ip 的服务器 => 通过 ngnix 域名解析 => 引导到不同的服务监听端口;

二、服务涉及到网络优化

手写并发 - QPS

面试题:【并发优化】10 个请求,由于后台或者业务需求只能同时执行三个

分析:

  • 输入:promise 数组、limit 参数;
  • 存储:reqpool - 并发池;
  • 思路:塞入 + 执行;
function qpsLimit(requestPipe, limitMax = 3) {
  debugger;

  let reqPool = [];
  // let reqMap = new Map();

  // 往并发池里塞入 promise
  const add = () => {
    let _req = requestPipe.shift();
    reqPool.push(_req);
  };

  // 执行实际请求
  const run = () => {
    if (requestPipe.length === 0) return;

    // 池子满了发车后,直接 race
    let _finish = Promise.race(reqPool);

    console.log(_finish);

    _finish.then(res => {
      // 做一个 id 整理
      let _done = reqPool.indexOf(_finish);
      reqPool.splice(_done, 1);
      add();
    });
    run();
  };

  while (reqPool.length < limitMax) {
    add();
  }
  run();
}

三、浏览器渲染时

浏览器执行顺序

  • 主线:HTML => DOM + CSSOM => renderTree + js => layout => paint

  • 支线:

    • repaint(重绘) - 改变文本、颜色等展示;
    • reflow(重排) - 元素几何尺寸变了;
  • 优化点: 减少 repaint,避免 reflow

    • display: none; => reflow
    • visibility: hidden; => repaint

四、脚本执行时 - JS

垃圾回收:mark & sweep 方案 => 触达标记,锁定清空、未触达直接抹掉。

// 内存分配:申明变量、函数、对象
// 内存使用:读写内存
// 内存释放

const zhaowa = {
  js: {
    performance: 'good',
    teacher: '云隐'
  }
};

// 建立引用关系
const _obj = zhaowa;

// 引用源给替换掉了 - 暂未 gc
zhaowa = 'best';

// 深入层级做引用 - 暂未 gc
const _class = _obj.js;

// 引用方替换 - 暂未 gc
_obj = 'over';

// gc 完成
_class = null;

// 建议:
//   1. 对象层级,宜平不宜深
//   2. 深层引用最好深拷贝,或者用完直接销毁
//   3. 避免循环引用
function traverseTree(node1, node2) {
  // 循环引用
  node1.parent = node2;
  node2.children = node1;
}

// 内存泄露
// 1、莫名其妙的全局变量
function foo() {
  bar1 = '';
  this.bar2 = '';
}

// 2、未清理的定时器
setInterval(() => {}, 1000);

// 3、使用后的闭包
function zhaowa() {
  let _no = 1;
  return () => {
    return _no++;
  };
}

五、打包配置优化

  • 懒加载:非必要不加载;
  • 按需引入:非必要不引入;
  • 抽离公共:相同项目合并公用;