jsliang 求职系列 - 23 - 性能优化

一 目录

不折腾的前端,和咸鱼有什么区别css

目录
一 目录
二 前言
2.1 DNS 解析
2.2 TCP 链接
2.3 发送 HTTP 请求
2.4 服务器响应
2.5 浏览器解析渲染页面
2.6 其余
2.7 小结
三 浏览器缓存
3.1 缓存位置
3.2 缓存机制
四 Cookie、Web Storage 和 IndexDB
4.1 Cookie
4.2 Local Storage
4.3 Session Storage
4.4 IndexDB
五 CDN
六 负载均衡
七 Webpack 优化
7.1 针对 Webpack 自己构建优化
  7.1.1 优化 resolve.modules 配置
  7.1.2 优化 resolve.extensions 配置
7.2 经过 Loader 和 Plugin 优化
  7.2.1 babel-loader
  7.2.2 tree shaking
  7.2.3 可视化分析
  7.2.4 缓存
  7.2.5 多进程
  7.2.6 抽离
  7.2.7 多进程代码压缩
  7.2.8 拆包
  7.2.9 打包资源压缩
  7.2.10 按需加载
7.3 优化体验
八 图片优化
8.1 JPEG 与 JPG
8.2 PNG-8 与 PNG-24
8.3 GIF
8.4 SVG
8.5 Base64
8.6 雪碧图
8.7 WebP
九 Gzip 压缩
十 服务端渲染
10.1 客户端渲染和服务端渲染
10.2 解决的性能问题
10.3 如何使用服务端渲染
10.4 服务端渲染小结
十一 浏览器渲染机制
11.1 浏览器渲染步骤
11.2 优化 - CSS 选择器问题
11.3 优化 - CSS 加载问题
11.4 优化 - JS 加载问题
11.5 优化 - DOM 渲染问题
十二 预加载页面资源
十三 长列表
13.1 懒加载
13.2 可视区域渲染
十四 性能监控
十五 参考文献
15.1 Webpack 优化
15.2 其余优化

二 前言

返回目录
  • 面试官:讲讲前端性能优化?

要提及前端性能优化,其实咱们能够从 “输入 URL 到页面呈现” 这个知识点着手讲起。html

在用户输入 URL,按下回车以后,走过的步骤:前端

  1. DNS 解析
  2. TCP 链接
  3. 发送 HTTP 请求
  4. 服务器响应
  5. 浏览器解析渲染页面

这其中能够作到哪些优化呢?vue

jsliang 在这里将这些知识点一锅炖,看你吃下多少。node

2.1 DNS 解析

返回目录

DNS 解析过程是一个知识点,详细可看:计算机网络 - DNSreact

首先须要知道的是 DNS 解析的开始步骤:浏览器 DNS 缓存 -> 系统缓存(host) -> 路由器缓存webpack

浏览器 DNS 缓存:你不肯定,也没法帮用户缓存;git

系统缓存(host):你本身修改 host 文件都要权限,修改用户的就更不靠谱了;github

路由器缓存:用户家的路由器……web

而后本地服务器向根服务器、顶级域名服务器、主域名服务器这些的请求就更不用说了,前端无法接触。

因此这个步骤咱们忽略先。

2.2 TCP 链接

返回目录

计算机网络 - TCP 3 次握手和 4 次挥手……

这个步骤咱们也忽略,前端性能优化暂时管不到它。

2.3 发送 HTTP 请求

返回目录

发送 HTTP 请求这块,咱们能够经过 4 点进行讲解:

  • 浏览器缓存

HTTP 请求发起的时候,咱们能够利用浏览器缓存,看采用强缓存仍是协商缓存,这样咱们对于有缓存的页面能够快速加载。

  • Cookie 和 WebStorage

利用 CookieWebStorage 对一些可有可无的数据进行缓存,方便利用。

  • CDN 的使用

静态资源的请求能够采用 CDN,减小服务器压力、防止没必要要携带 Cookie 的场景等。

  • 负载均衡

利用负载均衡的特色,开启 Node.js 方面的 PM2 或者 Nginx 的反向代理,轮询服务器,平均各个服务器的压力。

2.4 服务器响应

返回目录

在服务器响应的时候,咱们也能够作 4 部分:

  • Webpack 优化

在发布项目到服务器以前,咱们能够利用一些可视化插件进行分析,使用 Happypack 等提升打包效率,项目内容上能够作按需加载、tree shaking 等。

  • 图片优化

咱们须要熟悉了解 JPG/JPEGPNG-8/PNG-24GIFBase64SVG 这些图片的特性,而后经过 Webpack 的 url-loader 将一些小图标转换成 Base64,一些 Icon 使用 SVG,一些轮播图、Banner 图用 JPG/JPGE、雪碧图的使用等。

  • Gzip 压缩

Gzip 压缩的原理是在一个文本文件中找一些重复出现的字符串、临时替换它们,从而使整个文件变小(对于图片等会处理不了)。咱们能够经过 Webpack 的 ComparessionPlugin 进行 Gzip 压缩,而后在 Nginx 上进行配置,就能够利用好 Gzip 了。

  • 服务端渲染(SSR)

服务端渲染是指浏览器请求的时候,服务端将已经渲染好的 HTML 页面直接返回给浏览器,浏览器直接加载它的 HTML 渲染便可,减小了先后端交互,对 SEO 更友好。

2.5 浏览器解析渲染页面

返回目录

浏览器解析渲染页面的过程是:

  1. 解析 HTML,生成 DOM
  2. 解析 CSS,生成 CSS 规则树(CSS Rule Tree)
  3. DOM TreeCSS Rule Tree 相结合,生成 渲染树Render Tree
  4. 从根节点开始,计算每个元素的大小、位置,给出每一个节点所应该出现的屏幕精确坐标,从而获得基于渲染树的 布局渲染树Layout of the render tree)。
  5. 遍历渲染树,将每一个节点用 UI 渲染引擎来绘制,从而将整棵树绘制到页面上,这个步骤叫 绘制渲染树Painting the render tree

关于这个步骤咱们的优化方案有:

  1. CSS 选择器解析问题。编码过程当中用尽量少的选择器来表示一个元素,由于 CSS 是从右往左加载的。
  2. CSS 加载问题。尽量在 head 位置加载 CSS,减小 HTML 加载完毕须要等待 CSS 加载的问题。
  3. JS 加载问题。JS 的加载会阻塞 HTML 和 CSS 的加载,因此 script 标签一般放 body 后面,同时能够利用 script 标签的 asyncdefer 属性,同步加载 JS 或者等 HTML 和 CSS 加载渲染完后再加载 JS。
  4. DOM 渲染问题。DOM 渲染的时候可能会触发回流和重绘,应该尽可能避免触发。

如何避免触发回流:

  1. 【CSS】使用 visibility 替换 display
  2. 【CSS】避免 table 布局。对于 Render Tree 的计算一般只须要遍历一次就能够完成,可是 table 布局须要计算屡次,一般要花 3 倍于等同元素的时间,所以要避免。
  3. 【JS】避免频繁作 widthheight 等会触发回流的操做。
  4. 【JS】操做 DOM 的时候,若是是添加 DOM 节点,能够将全部节点都在 JS 中操做完毕,再进行渲染(一次性)

2.6 其余

返回目录

除此以外,咱们还能够经过:

  • Chrome 插件可视化判断页面哪些部分可进行优化
  • 长列表使用懒加载
  • preload 预加载页面

等进行性能优化相关操做。

2.7 小结

返回目录

以上,咱们就经过 6 个部分,串起来说解了前端性能优化部分的知识点。

固然,确定有咱们没有顾及的地方,欢迎小伙伴评论留言吐槽或者私聊 jsliangjsliang 会逐步完善这块内容。

下面咱们逐一详细的过一下上面讲到的优化知识点。

三 浏览器缓存

返回目录

浏览器缓存能够简单地理解为 HTTP 缓存。

3.1 缓存位置

返回目录

浏览器缓存位置分 4 个部分:

  • Service Worker Cache - 运行在浏览器背后的独立线程。通常能够用来实现缓存功能。
  • Menory Cache - 内存中的缓存。主要是页面上已经下载的样式、脚本、图片等已经抓取到的资源。
  • Disk Cache - 硬盘中的缓存。读取速度相对慢点。
  • Push Cache - 推送缓存。 是 HTTP2 中的内容,当以上 3 种缓存都没有命中的时候,它才会被使用。

3.2 缓存机制

返回目录
  • 强缓存

强缓存优先于协商缓存进行,若强制缓存生效则直接使用缓存,若不生效则进行协商缓存。强缓存不会向服务器发送请求,直接从缓存中读取资源。

强缓存利用 HTTP 请求头的 ExpiresCache-Control 两个字段来控制。

  • 协商缓存

协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么该请求的缓存失效,返回 200,从新返回资源和缓存标识,再存入浏览器中;生效则返回 304,继续使用缓存。

协商缓存利用 Last-Modified + If-Modified-SinceEtag + If-None-Match 来实现。

具体的缓存过程小伙伴们能够看浏览器缓存篇章,这里就不哆嗦了:

四 Cookie、Web Storage 和 IndexDB

返回目录

4.1 Cookie

返回目录

Cookie 最开始被设计出来其实并非来作本地存储的,而是为了弥补 HTTP 在状态管理上的不足。

Cookie 本质上就是浏览器里面存储的一个很小的文本文件,内部以键值对的方式来存储。

向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。

缺陷:

  1. 容量缺陷。体积上线 4kb,只能存储少许信息。
  2. 性能缺陷Cookie 请求每次都会携带上完整的 Cookie,随着请求数增多,形成性能浪费。
  3. 安全缺陷。以纯文本的形式在浏览器和服务器中传递,容易被非法截获和篡改。

4.2 Local Storage

返回目录

Local Storge 也是针对同一个域名。

同一个域名下,会存储相同的一段 Local Storage

相比 Cookie 优点:

  1. 容量。体积上线 5M,大于 Cookie4kb
  2. 只存在客户端。不参与和服务端的通信,避免 Cookie 的性能缺陷和安全缺陷。
  3. 接口封装。有 setItemgetItem 两个 API 接口。

应用场景

  • Base64 方式存储官方 Logo 等图片。

4.3 Session Storage

返回目录

基本上和 Local Stoarge 一致。

相比较上的不一样:

  • 会话级别的存储。不一样于 Local Storage 的持续化存储,Session Storage 当页面关闭的时候就不复存在了。

应用场景

  1. 对表单信息作维护。用户刷新页面不丢失。
  2. 存储本次浏览记录。看过的页面不怕找不到。

4.4 IndexDB

返回目录

IndexedDB 是运行在浏览器中的 非关系型数据库

由于本质上是数据库,因此通常来讲容量是没有上线的。

五 CDN

返回目录

CDN(Content Delivery Network,内容分发网络)指的是一组分布在各个地区的服务器。

这些服务器存储着数据的副本,所以服务器能够根据哪些服务器与用户距离最近,来知足数据的请求。

CDN 提供快速服务,较少受高流量影响。

假设有一部影片出版,很是多人看。jsliang 在广州,请求上海的服务器,结果这个服务器很是多人,资源响应地很慢。因而 jsliang 切换了路线,看到深圳服务器也有这个资源,因而向深圳服务器请求,结果能很快地看到这部影片。

在这个场景中,深圳服务器就扮演 CDN 的角色。

CDN 的核心:缓存回源

  • 缓存:将资源 copy 一份到 CDN 服务器。
  • 回源:CDN 发现本身没有这个资源,转头向根服务器(上级服务器)请求这个资源。

应用场景

  1. 公司静态资源部署到就近的服务器,利用 CDN 特性方便访问
  2. jQuery 等框架能够引用 CDN,加快网站的加载速度,避免同一个服务器加载的限制。
  3. 减小 Cookie 影响。同一个域名下,请求静态资源会携带 Cookie 信息,可是咱们并不须要,因此使用 CDN 能够避免没必要要的 Cookie 出现场景。

六 负载均衡

返回目录

若是是大型网站,负载均衡是不可或缺的内容。

  • PM2:一款 Node.js 进程管理器,让计算机每个内核都启动一个 Node.js 服务,而且实现自动控制负载均衡。
  • Nginx:经过轮询机制,将用户的请求分配到压力较小的服务器上(反向代理)。

区别:反向代理是对服务器实现负载均衡,而 PM2 是对进程实现负载均衡。

七 Webpack 优化

返回目录

Webpack 的优化瓶颈,主要是 2 个方面:

  • Webpack 的构建过程太花时间
  • Webpack 打包的结果体积太大

7.1 针对 Webpack 自己构建优化

返回目录

7.1.1 优化 resolve.modules 配置

返回目录

resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块,默认是 ['node_modules'],可是,它会先去当前目录的 ./node_modules 查找,没有的话再去 ../node_modules,最后到根目录。

因此能够直接指定项目根目录,就不须要一层一层查找。

resolve: {
  modules: [path.resolve(__dirname, 'node_modules')],
}

7.1.2 优化 resolve.extensions 配置

返回目录

在导入没带文件后缀的路径时,Webpack 会自动带上后缀去尝试询问文件是否存在,而 resolve.extensions 用于配置尝试后缀列表;默认为 extensions:['js', 'json']

当遇到 require('./data')Webpack 会先尝试寻找 data.js,没有再去找 data.json;若是列表越长,或者正确的后缀越日后,尝试的次数就会越多。

因此在配置时为提高构建优化需遵照:

  1. 频率出现高的文件后缀优先放在前面。
  2. 列表尽量的少,例如只有 3 个:jsjsxjson
  3. 书写导入语句时,尽可能写上后缀名。

7.2 经过 Loader 和 Plugin 优化

返回目录

7.2.1 babel-loader

返回目录

babel-loader 为例,能够经过 includeexclude 帮助咱们避免 node_modules 这类庞大文件夹。

7.2.2 tree shaking

返回目录

经过 ES6 的 import/export 来检查未引用代码,以及 sideEffects 来标记无反作用代码,最后用 UglifyJSPlugin 来作 tree shaking,从而删除冗余代码。

7.2.3 可视化分析

返回目录
  • speed-measure-webpack-plugin:测量出在构建过程当中,每个 Loader 和 Plugin 的执行时长。
  • webpack-bundle-analyzer:经过矩阵树图的方式将包内各个模块的大小和依赖关系呈现出来。
  • webpack-chart
  • webpack-analyse

7.2.4 缓存

返回目录
  • cache-loader

参考连接:cache-loader

babel-loader 开启 cache 后,将 loader 的编译结果写进硬盘缓存,再次构建若是文件没有发生变化则会直接拉取缓存。

  • uglifyjs-webpack-plugin

也能够解决缓存问题。

7.2.5 多进程

返回目录

Happypack 能够将任务分解成多个子进程去并发执行,大大提高打包效率。

7.2.6 抽离

返回目录

经过 DllPlugin 或者 Externals 进行静态依赖包的分离。

因为 CommonsChunkPlugin 每次构建会从新构建一次 vendor,因此出于效率考虑,使用 DllPlugin 将第三方库单独打包到一个文件中,只有依赖自身发生版本变化时才会从新打包。

7.2.7 多进程代码压缩

返回目录

由于自带的 UglifyJsPlugin 压缩插件是单线程运行的,而 ParallelUglifyPlugin 能够并行执行。

因此经过 ParallelUglifyPlugin 代替自带的 UglifyJsPlugin 插件。

7.2.8 拆包

返回目录

Webpack 中,到底什么是代码分离?代码分离容许你把代码拆分到多个文件中。若是使用得当,你的应用性能会提升不少。由于浏览器能缓存你的代码。

每当你作出一次修改,包含修改的文件须要被全部访问你网站的人从新下载。但你并不会常常修改应用的依赖库。

若是你能把那些依赖库拆分到彻底分离的文件中,即便业务逻辑发生了更改,访问者也不须要再次下载依赖库,直接使用以前的缓存就能够了。

因为有了 SplitChunksPlugin,你能够把应用中的特定部分移至不一样文件。若是一个模块在不止一个 chunk 中被使用,那么利用代码分离,该模块就能够在它们之间很好地被共享。

7.2.9 打包资源压缩

返回目录
  • JS 压缩:UglifyJSPlugin
  • HTML 压缩:HtmlWebpackPlugin
  • 提取公共资源:splitChunks.cacheGroups
  • CSS 压缩:MiniCssExtractPlugin
  • Gzip 压缩:不包括图片

7.2.10 按需加载

返回目录

经过 Code-Splitting 来作 React 的按需加载.

Code_Splitting 核心是 require-ensure

7.3 优化体验

返回目录
  • progress-bar-webpack-plugin:在终端底部,将会有一个构建的进度条,可让你清晰的看见构建的执行进度。
  • webpack-build-notifier:在构建完成时,可以像微信、Lark 这样的 APP 弹出消息的方式,提示构建已经完成。
  • webpack-dashboard:对 Webpack 原始的构建输出不满意的话,也可使用这样一款 Plugin 来优化你的输出界面。

八 图片优化

返回目录

8.1 JPEG 与 JPG

返回目录
  • 关键字:有损压缩、体积小、加载快、不支持透明
  • 优势:压缩必定程度能保持品质、体积小、请求速度快
  • 缺点:处理矢量图形、Logo 等线条感较强,颜色对比强烈的图形,人为压缩会致使图片模糊明显。不支持透明度处理。
  • 使用场景:大的背景图、轮播图或者 Banner 图。

8.2 PNG-8 与 PNG-24

返回目录
  • 关键字:无损压缩、质量高、体积大、支持透明
  • 优势:PNG-8 支持 256 种颜色,PNG-24 支持 1600 种颜色。更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。
  • 缺点:体积较大
  • 使用场景:Logo、颜色简单且对比强烈的图片和背景。

8.3 GIF

返回目录
  • 关键字:动态图、体积小支持透明
  • 优势:能够压缩体积很是小。可插入多帧实现动画效果。支持透明色浮现于背景之上。
  • 缺点:最多只能处理 256 中颜色,不适用于真彩图像。
  • 使用场景:小动画。

8.4 SVG

返回目录
  • 关键字:文本文件、体积小、不失真、兼容性好
  • 优势:文本体积更小,可压缩性更强。图片能够无限放大不失真。文本文件能够直接在 HTML 中写入,灵活性高。
  • 缺点:渲染成本高、学习成本(可编程)
  • 使用场景:变成代码嵌入 HTML 中,也能够换成 .svg 后缀的文件进行引用。

8.5 Base64

返回目录
  • 关键字:文本文件、依赖编码、小图标解决方案
  • 优势:做为雪碧图的补充而存在,减小加载页面图片时对服务器的请求次数。(img src 会发起资源请求,可是 Base64 获得的是字符串,嵌入 HTML 中)
  • 缺点:大图使用 Base64 会增大致积,影响性能
  • 使用场景:小 Logo(不超过 2kb)、更新频率低的图片。
  • 编码工具:Webpack 的 url-loader 能够根据文件大小来判断是否编码成 Base64。

8.6 雪碧图

返回目录

雪碧图、CSS 精灵、CSS Sprites、图像精灵,都是同一个玩意。

它是将小图标和背景图像合并到一张图片上,而后经过 CSS 背景定位来显示其中的每个具体部分。

它是一种优化手段,由于单张图片所需的 HTTP 请求更少,对内存和带宽更加友好。

8.7 WebP

返回目录
  • 关键字:年轻的全能型选手
  • 优势:支持有损压缩和无损压缩、支持透明、能够跟 GIF 同样显示动态图
  • 缺点:兼容性差
  • 使用场景:暂无大型应用场景

九 Gzip 压缩

返回目录
  • Webpack 开启 Gzip

经过 compression-webpack-plugin 能够开启 Gzip 压缩。

  • 是否值得开启 Gzip

若是压缩文件过小,那不使用;可是若是具备必定规模的项目文件,能够开启 Gzip。

  • Gzip 原理

Gzip 并非万能的,它的原理是在一个文本文件中找一些重复出现的字符串、临时替换它们,从而使整个文件变小,因此对于图片等会处理不了。

  • 服务器端和 Webpack 的 Gzip 并存

服务器压缩也须要时间开销和 CPU 开销,因此有时候能够用 Webpack 来进行 Gzip 压缩,从而为服务器分压。

十 服务端渲染

返回目录
  • 什么是服务端渲染(服务端渲染的运行机制)
  • 为何要用服务端渲染(服务端渲染解决了什么性能问题)
  • 怎么作服务端渲染(服务端渲染的应用实例和使用场景)

10.1 客户端渲染和服务端渲染

返回目录

客户端渲染中,页面上呈现的内容,在 HTML 源文件中每每找不到。

而服务端渲染,当用户第一次请求页面时,服务器会把须要的组件或者页面渲染成 HTML 字符串,返回给客户端。

即客户端直接拿到 HTML 内容,而不须要跑一遍 JS 去生成 DOM 内容。

“所见即所得”,服务端渲染情景下,页面上呈现的内容,在 HTML 源文件里面也能够找到。

10.2 解决的性能问题

返回目录

假设 A 网站关键字上有 前端性能优化,可是这篇文章只有 A 网站服务器搜索事后才会出来结果,这时候搜索引擎是没法找到的。

为了更好的 SEO 效果,就要拿 “现成的内容” 给搜索引擎看,就要开启服务端渲染。

其次,服务端渲染解决了一个性能问题 —— 首屏加载速度过慢。

从输入 URL 到页面渲染过程当中咱们知道,若是是客户端渲染,咱们须要加载 HTML、CSS,而后再通过 JS 造成 Render Tree,定位后再绘制页面。

这个过程当中用户一直在等待,若是采用了服务端渲染,那么服务端能够直接给一个能够拿来呈现给用户的页面。

10.3 如何使用服务端渲染

返回目录
  • 如何给 React 开启服务端渲染
  • 如何给 Vue 开启服务端渲染

给 React 开启:

前端项目 - VDOM.js
import React from 'react';

const VDom = () => {
  return <div>我是一个被渲染为真实 DOM 的虚拟 DOM</div>
};

export default VDom;
Node 项目 - index.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import VDom from './VDom';

// 建立一个 express 应用
const app = express();

// renderToString 是把虚拟 DOM 转化为真实 DOM 的关键方法
const RDom = renderToString(<VDom />);

// 编写 HTML 模板,插入转化后的真实 DOM 内容
const Page = `
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <span>服务端渲染出了真实 DOM:  </span>
    ${RDom}
  </body>
</html>
`;
            
// 配置 HTML 内容对应的路由
app.get('/index', function(req, res) {
  res.send(Page)
});

// 配置端口号
const server = app.listen(8000);

VDom 组件已经被 renderToString 转化为了一个内容为 <div data-reactroot="">我是一个被渲染为真实 DOM 的虚拟 DOM</div> 的字符串,这个字符串被插入 HTML 代码,成为了真实 DOM 树的一部分。

至于 Vue 的能够看:服务器端渲染 (SSR)?

不熟悉 Vue,就不哆嗦了。

10.4 服务端渲染小结

返回目录

SSR 主要用于解决单页应用首屏渲染慢以及 SEO 问题,同时也解决了与后端同窗的沟通成本。但同时:提升了服务器压力,吃 CPU,内存等资源,优化很差提升成本。

十一 浏览器渲染机制

返回目录

浏览器内核决定了浏览器解释网页语法的方式。

目前常见的浏览器内核有:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。

11.1 浏览器渲染步骤

返回目录

如上图,浏览器的渲染过程为:

  1. 解析 HTML,生成 DOM
  2. 解析 CSS,生成 CSS 规则树(CSS Rule Tree)
  3. DOM TreeCSS Rule Tree 相结合,生成 渲染树Render Tree
  4. 从根节点开始,计算每个元素的大小、位置,给出每一个节点所应该出现的屏幕精确坐标,从而获得基于渲染树的 布局渲染树Layout of the render tree)。
  5. 遍历渲染树,将每一个节点用 UI 渲染引擎来绘制,从而将整棵树绘制到页面上,这个步骤叫 绘制渲染树Painting the render tree

11.2 优化 - CSS 选择器问题

返回目录

咱们正常的阅读顺序是从左往右的,可是 CSS 解析器解析 CSS 的时候,采用的是古人的规则。

#ul li {}

这样的一行规则,咱们写起来的时候很顺畅:先找 idul 的元素,再找里面的 li 元素。

可是实际上 CSS 解析器是从右往左的,它会先查找全部 li 元素,而且逐个确认这个 li 元素的父元素的 id 是否是 ul,这就坑死了。

因此像通配符 * { padding: 0; margin: 0 } 这种,小伙伴们就应该减小设置,要否则页面的元素越多遍历匹配越久。

总结一下:

  • 避免使用通配符 * 等。
  • 减小使用标签选择器,用类选择器或者标签选择器替代,例如 span 替换为 .span
  • 减小嵌套匹配,例如 #ul li a

11.3 优化 - CSS 加载问题

返回目录

为了不 HTML 解析完毕,可是 CSS 没有解析完毕,从而致使页面直接 “裸奔” 在用户面前的问题,浏览器在处理 CSS 规则树的时候,不会渲染任何已处理的内容。

因此不少时候,咱们会让网页尽早处理 CSS,即在 head 标签中启用 link 或者启用 CDN 实现静态资源加载速度的优化。

11.4 优化 - JS 加载问题

返回目录

在上面的加载过程当中咱们并无提到 JS,实际上 JS 会对 DOM 和 CSSDOM 进行修改,所以 JS 的执行会阻止 CSS 规则树的解析,有时候还会阻塞 DOM。

实际上,当 HTML 解析器赶上 script 标签时,它会暂停解析过程,将控制器交给 JS 引擎。

若是是内部的 JS 代码,它会直接执行,可是若是是 src 引入的,还要先获取脚本,再进行执行。

等 JS 引擎执行完毕后,再交接给渲染引擎,继续 HTML 树和 CSS 规则树的构建。

这样一来一回交接,并且有时候 JS 执行过多还会卡慢,进而致使页面渲染变慢。

因此咱们能够经过 async 异步加载完 JS 脚本,再执行里面内容;或者经过 defer 等整个文档解析完毕后,再执行这个 JS 文件。

若是 JS 和 DOM 元素或者其余 JS 代码之间的依赖不强的时候,使用 async

若是 JS 依赖于 DOM 元素和其余 JS 的执行结果,那就使用 defer

11.5 优化 - DOM 渲染问题

返回目录

当使用 JS 去操做 DOM 的时候,其实是 JS 引擎和渲染引擎之间的沟通,这个沟通的过程要开销的。

每操做一次 DOM 就收费一次,多了页面就卡起来咯。

同时,操做 DOM 的时候修改了尺寸等元素,还会引发回流和重绘。

  • 回流(reflow):又叫重排(layout)。当元素的尺寸、结构或者触发某些属性时,浏览器会从新渲染页面,称为回流。此时,浏览器须要从新通过计算,计算后还须要从新页面布局,所以是较重的操做。
  • 重绘(repaint):当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时因为只须要 UI 层面的从新像素绘制,所以损耗较少

什么操做触发回流?

  1. 添加删除 DOM 元素
  2. 改变边框、边距、宽高(bordermarginpaddingwidthheight
  3. 浏览器改变窗口(resize
  4. ……等

什么操做触发重绘?

  1. 修改背景色、颜色(backgroundcolor
  2. 设置可见度(visibility
  3. 设置背景图(background-image
  4. ……等

咱们仔细看这张图,能够看到重排(Layout)会致使 Render Tree 重构,进而触发重绘(Painting):

  • 回流一定重绘,重绘不必定回流

所以,咱们操做 DOM 的时候,能够这么优化:

  1. 【CSS】使用 visibility 替换 display
  2. 【CSS】避免 table 布局。对于 Render Tree 的计算一般只须要遍历一次就能够完成,可是 table 布局须要计算屡次,一般要花 3 倍于等同元素的时间,所以要避免。
  3. 【JS】避免频繁作 widthheight 等会触发回流的操做。
  4. 【JS】操做 DOM 的时候,若是是添加 DOM 节点,能够将全部节点都在 JS 中操做完毕,再进行渲染(一次性)

十二 预加载页面资源

返回目录

preload 提供了一种声明式的命令,让浏览器提早加载指定资源(加载后并不执行),在须要执行的时候再执行。

提供的好处主要是:

  • 将加载和执行分离开,可不阻塞渲染和 documentonload 事件
  • 提早加载指定资源,再也不出现依赖的 font 字体隔了一段时间才刷出
<!-- 使用 link 标签静态标记须要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">

<!-- 或使用脚本动态建立一个 link 标签后插入到 head 头部 -->
<script>
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  link.href = '/path/to/style.css';
  document.head.appendChild(link);
</script>

在不支持 preload 的浏览器环境中,会忽略对应的 link 标签。

区分 preloadprefetch

  • preload:告诉浏览器页面一定须要的资源,浏览器必定会加载这些资源。
  • prefetch:告诉浏览器页面可能须要的资源,浏览器不必定会加载这些资源。

固然,开发中须要注意:

  • 避免滥用 preload
  • 避免混用 preloadprefetch
  • 避免错用 preload 加载跨域资源

十三 长列表

返回目录

13.1 懒加载

返回目录

懒加载实现思路:

  • div 经过背景图片设置为 none,起到占位的做用。
  • 出如今可视区域的时候,div 填写有效 URL。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Lazy-Load</title>
  <style>
    .img {
      width: 100px;
      height: 300px;
    }
    .img img {
      width: 200px;
      height: 400px;
    }
  </style>
</head>
<body>
  <div class="container">
    <!-- 注意咱们并无为它引入真实的 src -->
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png"></div>
    <div class="img"><img class="pic" alt="加载中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png"></div>
  </div>

  <script>
    (function() {
      // 获取全部的图片标签
      const imgs = document.getElementsByTagName('img');

      // 获取可视区域的高度(document.documentElement.clientHeight 是兼容低版本 IE)
      const viewHeight = window.innerHeight || document.documentElement.clientHeight;

      // num 用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
      let num = 0;

      const lazyload = () => {
        for (let i = num; i < imgs.length; i++) {

          // 用可视区域高度减去元素顶部距离可视区域顶部的高度
          let distance = viewHeight - imgs[i].getBoundingClientRect().top;

          // 若是可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
          if (distance >= 0){

            // 给元素写入真实的 src,展现图片
            imgs[i].src = imgs[i].getAttribute('data-src');

            // 前 i 张图片已经加载完毕,下次从第 i + 1 张开始检查是否露出
            num = i + 1;
          }
        }
      }

      // 首屏初始化
      lazyload();

      // 监听Scroll事件
      window.addEventListener('scroll', lazyload, false);
    })()
  </script>
</body>
</html>

还有其余方法,诸如:

  • img 标签自带的 loading 属性
  • InsectionObserver
  • 骨架屏

13.2 可视区域渲染

返回目录

无限滚动在移动端很常见,可是可见区域渲染并不常见,主要是由于 IOS 上 UIWebView 的 onscroll 并不能实时触发。

实现可见区域渲染的思路:

  1. 计算当前可见区域起始数据的 startIndex
  2. 计算当前可见区域结束数据的 endIndex
  3. 计算当前可见区域的数据,并渲染到页面中
  4. 计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上

代码实现:略。

十四 性能监控

返回目录
  1. Chrome 工具 Performance
  2. Chrome 插件 Page Speed
  3. 自动化工具 Lighthouse

    1. Chrome 拓展安装
    2. npm i lighthouse -glighthouse https://www.baidu.com
    3. Chrome 有基于 LightHouseAudits 面板

十五 参考文献

返回目录

本篇参考文献有 31 篇。

15.1 Webpack 优化

返回目录

2019 年文章

2018 年文章

2017 年文章

15.2 其余优化

返回目录

jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的做品创做。<br/>本许可协议受权以外的使用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处得到。
相关文章
相关标签/搜索