流月
  • CSS
  • JavaScript
  • Web API
  • TypeScript
  • 框架

    • React
    • Vue
  • 其他

    • 小程序
    • 工程化
    • 性能优化
    • 测试
    • 其他
  • nodejs
  • deno
  • express
  • nginx
  • docker
  • 其他
  • 安全基础
  • 正则表达式
  • 网络基础
  • 设计模式
  • 数据结构与算法
  • LeetCode
  • CodeWars
  • 手写代码
  • Git
  • devops
  • 编码原则
  • 防御编程
  • Chrome
  • Edge
  • Flutter
  • Linux
  • 库
  • 网站
  • 面试
  • 摘抄
  • 方法论
  • 语法
  • 王小波
  • Elon Musk
  • CSS
  • JavaScript
  • Web API
  • TypeScript
  • 框架

    • React
    • Vue
  • 其他

    • 小程序
    • 工程化
    • 性能优化
    • 测试
    • 其他
  • nodejs
  • deno
  • express
  • nginx
  • docker
  • 其他
  • 安全基础
  • 正则表达式
  • 网络基础
  • 设计模式
  • 数据结构与算法
  • LeetCode
  • CodeWars
  • 手写代码
  • Git
  • devops
  • 编码原则
  • 防御编程
  • Chrome
  • Edge
  • Flutter
  • Linux
  • 库
  • 网站
  • 面试
  • 摘抄
  • 方法论
  • 语法
  • 王小波
  • Elon Musk
  • 性能优化

性能优化

评测点

  • 评测点:首屏速度 白屏时间
  • 从 http 请求数量
  • http 请求体积
  • dom 操作层面
  • dom loaded事件

React 项目性能优化

先分析

  • bundle 分析 webpack-bundle-analyzer
  • chrome performace
  • react profiler 分析 render duration

代码层面

  • 避免不必要的 render
    • shouldComponentUpdate/React.PureComponent 为 class 组件服务
      • 浅比较(比较对象的引用)
    • React.memo(Comp) 为函数组件服务 对 Props 进行浅比较
        const OtherComponent = React.memo(()=>{
            ...
        });
      
    • 善用 React.useMemo
      const App = (props)=>{
        const [boolean, setBoolean] = useState(false);
        const [start, setStart] = useState(0);
        
        // 这是一个非常耗时的计算
        // 调用 setBoolean 组件会重新 render,又会执行这个耗时的运算
        const result = computeExpensiveFunc(start);
    
        // 改进,当 start 变化时,才允许计算新的 result
        const result = useMemo(()=>computeExpensiveFunc(start), [start]);
      }
    
    • 合理使用 React.useCallback
      // 虽然 OtherComponent 已经用 React.memo 包裹起来了,但在父组件每次触发 setBoolean 时, OtherComponent 仍会频繁 render
      // 因为父级组件 onChange 函数在每一次 render 时,都是新生成的,导致子组件浅比较失效。通过 React.useCallback,我们可以让 onChange 只有在 state 变化时,才重新生成
      const OtherComponent = React.memo(()=>{
          ...
      });
        
      const App = (props)=>{
        const [boolan, setBoolean] = useState(false);
        const [value, setValue] = useState(0);
      
        // 错误 ❌
        const onChange = (v)=>{
            axios.post(`/api?v=${v}&state=${state}`)
        }
      
        // 正确 ✔️
        const onChange = React.useCallback((v)=>{
          axios.post(`/api?v=${v}&state=${state}`)
        }, [state])
      
      
        return (
          <div>
              {/* OtherComponent 是一个非常昂贵的组件 */}
              <OtherComponent onChange={onChange}/>
          </div>
        )
      }
      

打包优化

  • code spliting (SplitChunksPlugin) 默认开启
  • tree shaking 默认开启

分析

性能优化层面

  • 缓存层面
  • 网络层面
  • 渲染层面

DOM 层面

  • 缓存DOM元素,提高页面性能
  • CSS3 transform 取代传统DOM节点操作 使用GPU硬件加速
  • 减少reflow/repaint
  • 避免不必要的dom操作 使用domfragment

网络层面

  • link preload prefetch
  • script async defer script标签再也不要放到页脚了 笑
  • 懒加载 code spliting / 图片懒加载
  • 压缩混淆css/js -> 减小http体积
  • 图片 使用css精灵图/字体图标/base64
  • 缓存 pwa / http cache

优化网络

  • 减少资源体积
    • gzip 压缩
    • 打包优化
    • 图片格式
    • 使用 HTTP2
  • 加快请求速度
    • HTTP2 多路复用
  • 减少请求
    • 缓存
      • 强缓存和协商缓存
      • CDN
      • 本地存储
    • 图片
      • 内联 base64
      • 雪碧图
    • 异步加载
      • script async/defer

JS 性能优化

对 JS 性能影响最大的就是内存分配、垃圾收集和作用域访问。

  • 目的
    • 更少的内存占用
    • 编译时间少
    • 不要卡死事件队列
    • 不要让浏览器卡顿
    • 不要让浏览器响应编码
    • 浏览器是单进程的
  • 数据结构和算法的优化
  • 使用防抖和节流
  • 使用 requestFrame,把任务切分到每一帧
  • 使用 Worker,不在主线程进行大量运算
  • 使用 web assembly

渲染层面

  • 传统的服务端渲染存在的弊端?
  • 分块传输
    • http 1.1 中引入了一个 http 首部,Transfer-Encoding:chunked
    • 这个首部标识了实体采用chunked编码传输,chunked编码可以将实体分块儿进行传输,并且chunked编码的每一块内容都会自标识长度
    • 如果需要多个数据,而多个数据均返回较慢的话。可以处理完一块就返回一块,让浏览器尽早的接收到html,可以先行渲染
  • bigPipe
    • 回填的思路
    • 为什么不用ajax去请求后端,拉数据,劣势是什么?

服务端配置

HTTP 缓存

HTTPS

HTTP2

CDN 部署

服务端渲染 SSR

React 服务端渲染

Vue 服务端渲染

预渲染

和 SSR 比

实现方案

前端渲染性能

卡顿侦测

优化方案

减少计算

并行计算

  • WebGL
  • Web Worker

代码优化

  • 优化算法,调整结构

原理

http 的问题

  • 重复传输 header
    • 消息主体都会经过gzip压缩,或者本身传输的就是二进制文件
    • 状态行和头部没有经过任何压缩,以纯文本传输。而且大部分http的headers内容都是重复的
  • 明文传输 被中间人劫持

异步加载第三方内容

  (function(){
    var script,
        scripts = document.getElementByTagName('script')[0];
    function load(url){
      script = document.createElement('script');
      script.async = true;
      script.src = url;
      scripts.parentNode.insertBefore(script, scripts);
    }

    load('//apis.plugin.js')
    load('//apis.plugin.js')
    load('//apis.plugin.js')
  })()

专栏 + 小册

  • 你不知道的前端性能优化技巧 - 慕课专栏
  • 前端性能优化原理与实践 - 掘金小册
    • 优化的概念
    • 性能优化工具
    • 性能监测
      • 可视化工具
        • Performance
        • LightHouse
      • W3C 性能 API
    • 网络部分
      • 请求过程的优化
        • Gzip压缩原理
        • 构建工具性能调优
        • DNS Prefetch
        • 图片加载优化
      • 减少网络请求
        • 本地存储
    • 缓存部分
      • CDN 缓存
      • 本地缓存
    • 渲染部分
      • 浏览器的渲染机制
        • CSS 性能方案
        • JS 性能方案
      • 服务端渲染的探索与实践
      • 避免首屏一片空白
      • DOM 性能优化
        • Event Loop 力挽狂澜
        • 重绘与回流
    • 应用实践
      • 防抖节流
      • 服务端渲染
      • 懒加载
      • React 优化

题目

1 面试官:webpack的gzip和nginx的有什么关系?

nginx没有开启gzip压缩,webpack打包出的.gz文件是用不到的2.nginx开启了gzip,nginx查找静态资源是否存在已经压缩好的gzip压缩文件,如果没有则自行压缩(消耗cpu但感知比较少)3.nginx开启gzip压缩,webpack打包出的.gz文件被找到,提前(打包)压缩直接使用,减少了nginx的压缩损耗

2 Expires和Cache-control优先级

Expires - 具体过期时间 Cache-control - 相对过期时间

若两者同时存在,取Cache-control

3 面试官:Cache-control有哪些属性了解过吗

no-cache public 可以被任何缓存区缓存 private 只能在浏览器中被缓存 代理服务器不可以缓存 max-age 相对过期时间 以秒为单位 s-maxage ?

4 面试官: Last-Modified和ETag的区别

Last-Modified计算方式比Etag简单,算法难度不一样

but 如果一个文件在1s内更新了多次,Last-Modified就不靠谱了,文件就被缓存住了

5 说说cdn原理 选取最近位置进行请求,节省对源站的消耗(回源)

6 图片转Base64是为了什么

小的图片 使用base64编码 可以减少一次图片的网络请求

大的图片 base64编码会和html混在一起 html内容会变大 因此webpack url-loader提供Limit的功能

而且base64编码的图片不能像正常的图片进行缓存 但是写在css里面 css文件可以缓存

7 面试官:说说 defer 和 async 的使用场景

  • defer 一边下载,等待dom解析完才执行,早于 DOMContentLoaded

大多数情况 对加载顺序有要求 都是要defer

async 下载完便执行,下载过程和 dom 解析并行

js执行都阻塞DOM渲染

async适合不依赖dom,第三方资源

8 面试官:preload 和 prefetch 的共同点和不同点

preload 优先级高

prefetch 浏览器空闲的时候 才会请求

9 http keep-alive / tcp keep-alive

HTTP Keep-Alive是一项功能,它允许HTTP客户端(通常是浏览器)和服务器(网络服务器)通过同一TCP连接发送多个请求/响应对。这减少了第二,第三,... HTTP请求的延迟,减少了网络流量等。

TCP keepalive是完全不同的野兽。它通过发送小数据包保持TCP连接打开。此外,在发送数据包时,这是一种检查,以便在连接断开时立即通知发送方

10 为什么HTTPS选择对称加密和非对称加密

非对称加密算法非常耗时,特别是加密解密一些较大数据的时候有些力不从心,而对称加密快很多,看来必须得用对称加密

11 HTTPS 必须在每次请求中都要先在SSL/TLS层进行握手传输密钥吗?

这也是我当时的困惑之一,显然每次请求都经历一次密钥传输过程非常耗时,那怎么达到只传输一次呢?

用session就行。

服务器会为每个浏览器(或客户端软件)维护一个session ID,在TSL握手阶段传给浏览器,浏览器生成好密钥传给服务器后,服务器会把该密钥存到相应的session ID下,之后浏览器每次请求都会携带session ID,服务器会根据session ID找到相应的密钥并进行解密加密操作,这样就不必要每次重新制作、传输密钥了!

Last Updated: 7/21/20, 12:45 PM
Contributors: bhaltair, wangqi