不折腾的前端,和咸鱼有什么区别css
返回目录
要提及前端性能优化,其实咱们能够从 “输入 URL 到页面呈现” 这个知识点着手讲起。html
在用户输入 URL
,按下回车以后,走过的步骤:前端
这其中能够作到哪些优化呢?vue
jsliang 在这里将这些知识点一锅炖,看你吃下多少。node
返回目录
DNS 解析过程是一个知识点,详细可看:计算机网络 - DNSreact
首先须要知道的是 DNS
解析的开始步骤:浏览器 DNS 缓存 -> 系统缓存(host) -> 路由器缓存webpack
浏览器 DNS
缓存:你不肯定,也没法帮用户缓存;git
系统缓存(host
):你本身修改 host
文件都要权限,修改用户的就更不靠谱了;github
路由器缓存:用户家的路由器……web
而后本地服务器向根服务器、顶级域名服务器、主域名服务器这些的请求就更不用说了,前端无法接触。
因此这个步骤咱们忽略先。
返回目录
这个步骤咱们也忽略,前端性能优化暂时管不到它。
返回目录
发送 HTTP
请求这块,咱们能够经过 4
点进行讲解:
HTTP
请求发起的时候,咱们能够利用浏览器缓存,看采用强缓存仍是协商缓存,这样咱们对于有缓存的页面能够快速加载。
利用 Cookie
和 WebStorage
对一些可有可无的数据进行缓存,方便利用。
静态资源的请求能够采用 CDN
,减小服务器压力、防止没必要要携带 Cookie
的场景等。
利用负载均衡的特色,开启 Node.js 方面的 PM2 或者 Nginx 的反向代理,轮询服务器,平均各个服务器的压力。
返回目录
在服务器响应的时候,咱们也能够作 4 部分:
在发布项目到服务器以前,咱们能够利用一些可视化插件进行分析,使用 Happypack
等提升打包效率,项目内容上能够作按需加载、tree shaking
等。
咱们须要熟悉了解 JPG/JPEG
、PNG-8/PNG-24
、GIF
、Base64
、SVG
这些图片的特性,而后经过 Webpack 的 url-loader
将一些小图标转换成 Base64
,一些 Icon 使用 SVG
,一些轮播图、Banner 图用 JPG/JPGE
、雪碧图的使用等。
Gzip 压缩的原理是在一个文本文件中找一些重复出现的字符串、临时替换它们,从而使整个文件变小(对于图片等会处理不了)。咱们能够经过 Webpack 的 ComparessionPlugin
进行 Gzip 压缩,而后在 Nginx 上进行配置,就能够利用好 Gzip 了。
服务端渲染是指浏览器请求的时候,服务端将已经渲染好的 HTML 页面直接返回给浏览器,浏览器直接加载它的 HTML 渲染便可,减小了先后端交互,对 SEO 更友好。
返回目录
浏览器解析渲染页面的过程是:
DOM
树CSS 规则树(CSS Rule Tree)
DOM Tree
和 CSS Rule Tree
相结合,生成 渲染树(Render Tree
)Layout of the render tree
)。Painting the render tree
)关于这个步骤咱们的优化方案有:
head
位置加载 CSS,减小 HTML 加载完毕须要等待 CSS 加载的问题。script
标签一般放 body
后面,同时能够利用 script
标签的 async
和 defer
属性,同步加载 JS 或者等 HTML 和 CSS 加载渲染完后再加载 JS。如何避免触发回流:
visibility
替换 display
table
布局。对于 Render Tree
的计算一般只须要遍历一次就能够完成,可是 table
布局须要计算屡次,一般要花 3 倍于等同元素的时间,所以要避免。width
、height
等会触发回流的操做。返回目录
除此以外,咱们还能够经过:
preload
预加载页面等进行性能优化相关操做。
返回目录
以上,咱们就经过 6 个部分,串起来说解了前端性能优化部分的知识点。
固然,确定有咱们没有顾及的地方,欢迎小伙伴评论留言吐槽或者私聊 jsliang,jsliang 会逐步完善这块内容。
下面咱们逐一详细的过一下上面讲到的优化知识点。
返回目录
浏览器缓存能够简单地理解为 HTTP
缓存。
返回目录
浏览器缓存位置分 4 个部分:
Service Worker Cache
- 运行在浏览器背后的独立线程。通常能够用来实现缓存功能。Menory Cache
- 内存中的缓存。主要是页面上已经下载的样式、脚本、图片等已经抓取到的资源。Disk Cache
- 硬盘中的缓存。读取速度相对慢点。Push Cache
- 推送缓存。 是 HTTP2 中的内容,当以上 3 种缓存都没有命中的时候,它才会被使用。返回目录
强缓存优先于协商缓存进行,若强制缓存生效则直接使用缓存,若不生效则进行协商缓存。强缓存不会向服务器发送请求,直接从缓存中读取资源。
强缓存利用 HTTP 请求头的 Expires
和 Cache-Control
两个字段来控制。
协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么该请求的缓存失效,返回 200,从新返回资源和缓存标识,再存入浏览器中;生效则返回 304,继续使用缓存。
协商缓存利用 Last-Modified + If-Modified-Since
和 Etag + If-None-Match
来实现。
具体的缓存过程小伙伴们能够看浏览器缓存篇章,这里就不哆嗦了:
返回目录
返回目录
Cookie
最开始被设计出来其实并非来作本地存储的,而是为了弥补 HTTP
在状态管理上的不足。
Cookie
本质上就是浏览器里面存储的一个很小的文本文件,内部以键值对的方式来存储。
向同一个域名下发送请求,都会携带相同的 Cookie
,服务器拿到 Cookie
进行解析,便能拿到客户端的状态。
缺陷:
4kb
,只能存储少许信息。Cookie
请求每次都会携带上完整的 Cookie
,随着请求数增多,形成性能浪费。返回目录
Local Storge
也是针对同一个域名。
同一个域名下,会存储相同的一段 Local Storage
。
相比 Cookie
优点:
5M
,大于 Cookie
的 4kb
。Cookie
的性能缺陷和安全缺陷。setItem
和 getItem
两个 API 接口。应用场景:
Base64
方式存储官方 Logo 等图片。返回目录
基本上和 Local Stoarge
一致。
相比较上的不一样:
Local Storage
的持续化存储,Session Storage
当页面关闭的时候就不复存在了。应用场景:
返回目录
IndexedDB
是运行在浏览器中的 非关系型数据库。
由于本质上是数据库,因此通常来讲容量是没有上线的。
返回目录
CDN(Content Delivery Network,内容分发网络)指的是一组分布在各个地区的服务器。
这些服务器存储着数据的副本,所以服务器能够根据哪些服务器与用户距离最近,来知足数据的请求。
CDN 提供快速服务,较少受高流量影响。
假设有一部影片出版,很是多人看。jsliang 在广州,请求上海的服务器,结果这个服务器很是多人,资源响应地很慢。因而 jsliang 切换了路线,看到深圳服务器也有这个资源,因而向深圳服务器请求,结果能很快地看到这部影片。
在这个场景中,深圳服务器就扮演 CDN 的角色。
CDN 的核心:缓存 和 回源。
copy
一份到 CDN 服务器。应用场景:
返回目录
若是是大型网站,负载均衡是不可或缺的内容。
PM2
:一款 Node.js 进程管理器,让计算机每个内核都启动一个 Node.js 服务,而且实现自动控制负载均衡。Nginx
:经过轮询机制,将用户的请求分配到压力较小的服务器上(反向代理)。区别:反向代理是对服务器实现负载均衡,而 PM2
是对进程实现负载均衡。
返回目录
Webpack 的优化瓶颈,主要是 2 个方面:
返回目录
返回目录
resolve.modules
用于配置 Webpack
去哪些目录下寻找第三方模块,默认是 ['node_modules']
,可是,它会先去当前目录的 ./node_modules
查找,没有的话再去 ../node_modules
,最后到根目录。
因此能够直接指定项目根目录,就不须要一层一层查找。
resolve: { modules: [path.resolve(__dirname, 'node_modules')], }
返回目录
在导入没带文件后缀的路径时,Webpack
会自动带上后缀去尝试询问文件是否存在,而 resolve.extensions
用于配置尝试后缀列表;默认为 extensions:['js', 'json']
。
当遇到 require('./data')
时 Webpack
会先尝试寻找 data.js
,没有再去找 data.json
;若是列表越长,或者正确的后缀越日后,尝试的次数就会越多。
因此在配置时为提高构建优化需遵照:
js
、jsx
、json
。返回目录
返回目录
以 babel-loader
为例,能够经过 include
和 exclude
帮助咱们避免 node_modules
这类庞大文件夹。
返回目录
经过 ES6 的 import/export
来检查未引用代码,以及 sideEffects
来标记无反作用代码,最后用 UglifyJSPlugin
来作 tree shaking
,从而删除冗余代码。
返回目录
speed-measure-webpack-plugin
:测量出在构建过程当中,每个 Loader 和 Plugin 的执行时长。webpack-bundle-analyzer
:经过矩阵树图的方式将包内各个模块的大小和依赖关系呈现出来。webpack-chart
webpack-analyse
返回目录
cache-loader
参考连接:cache-loader
在 babel-loader
开启 cache
后,将 loader
的编译结果写进硬盘缓存,再次构建若是文件没有发生变化则会直接拉取缓存。
uglifyjs-webpack-plugin
也能够解决缓存问题。
返回目录
Happypack
能够将任务分解成多个子进程去并发执行,大大提高打包效率。
返回目录
经过 DllPlugin
或者 Externals
进行静态依赖包的分离。
因为 CommonsChunkPlugin
每次构建会从新构建一次 vendor
,因此出于效率考虑,使用 DllPlugin
将第三方库单独打包到一个文件中,只有依赖自身发生版本变化时才会从新打包。
返回目录
由于自带的 UglifyJsPlugin
压缩插件是单线程运行的,而 ParallelUglifyPlugin
能够并行执行。
因此经过 ParallelUglifyPlugin
代替自带的 UglifyJsPlugin
插件。
返回目录
在 Webpack
中,到底什么是代码分离?代码分离容许你把代码拆分到多个文件中。若是使用得当,你的应用性能会提升不少。由于浏览器能缓存你的代码。
每当你作出一次修改,包含修改的文件须要被全部访问你网站的人从新下载。但你并不会常常修改应用的依赖库。
若是你能把那些依赖库拆分到彻底分离的文件中,即便业务逻辑发生了更改,访问者也不须要再次下载依赖库,直接使用以前的缓存就能够了。
因为有了 SplitChunksPlugin
,你能够把应用中的特定部分移至不一样文件。若是一个模块在不止一个 chunk
中被使用,那么利用代码分离,该模块就能够在它们之间很好地被共享。
返回目录
UglifyJSPlugin
HtmlWebpackPlugin
splitChunks.cacheGroups
MiniCssExtractPlugin
返回目录
经过 Code-Splitting 来作 React 的按需加载.
Code_Splitting
核心是 require-ensure
。
返回目录
返回目录
返回目录
返回目录
返回目录
返回目录
.svg
后缀的文件进行引用。返回目录
img src
会发起资源请求,可是 Base64 获得的是字符串,嵌入 HTML 中)url-loader
能够根据文件大小来判断是否编码成 Base64。返回目录
雪碧图、CSS 精灵、CSS Sprites、图像精灵,都是同一个玩意。
它是将小图标和背景图像合并到一张图片上,而后经过 CSS 背景定位来显示其中的每个具体部分。
它是一种优化手段,由于单张图片所需的 HTTP 请求更少,对内存和带宽更加友好。
返回目录
返回目录
经过 compression-webpack-plugin
能够开启 Gzip 压缩。
若是压缩文件过小,那不使用;可是若是具备必定规模的项目文件,能够开启 Gzip。
Gzip 并非万能的,它的原理是在一个文本文件中找一些重复出现的字符串、临时替换它们,从而使整个文件变小,因此对于图片等会处理不了。
服务器压缩也须要时间开销和 CPU 开销,因此有时候能够用 Webpack
来进行 Gzip 压缩,从而为服务器分压。
返回目录
返回目录
客户端渲染中,页面上呈现的内容,在 HTML 源文件中每每找不到。
而服务端渲染,当用户第一次请求页面时,服务器会把须要的组件或者页面渲染成 HTML 字符串,返回给客户端。
即客户端直接拿到 HTML 内容,而不须要跑一遍 JS 去生成 DOM 内容。
“所见即所得”,服务端渲染情景下,页面上呈现的内容,在 HTML 源文件里面也能够找到。
返回目录
假设 A 网站关键字上有 前端性能优化,可是这篇文章只有 A 网站服务器搜索事后才会出来结果,这时候搜索引擎是没法找到的。
为了更好的 SEO 效果,就要拿 “现成的内容” 给搜索引擎看,就要开启服务端渲染。
其次,服务端渲染解决了一个性能问题 —— 首屏加载速度过慢。
从输入 URL 到页面渲染过程当中咱们知道,若是是客户端渲染,咱们须要加载 HTML、CSS,而后再通过 JS 造成 Render Tree
,定位后再绘制页面。
这个过程当中用户一直在等待,若是采用了服务端渲染,那么服务端能够直接给一个能够拿来呈现给用户的页面。
返回目录
给 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,就不哆嗦了。
返回目录
SSR 主要用于解决单页应用首屏渲染慢以及 SEO 问题,同时也解决了与后端同窗的沟通成本。但同时:提升了服务器压力,吃 CPU,内存等资源,优化很差提升成本。
返回目录
浏览器内核决定了浏览器解释网页语法的方式。
目前常见的浏览器内核有:Trident
(IE)、Gecko
(火狐)、Blink
(Chrome、Opera)、Webkit
(Safari)。
返回目录
如上图,浏览器的渲染过程为:
DOM
树CSS 规则树(CSS Rule Tree)
DOM Tree
和 CSS Rule Tree
相结合,生成 渲染树(Render Tree
)Layout of the render tree
)。Painting the render tree
)返回目录
咱们正常的阅读顺序是从左往右的,可是 CSS 解析器解析 CSS 的时候,采用的是古人的规则。
#ul li {}
这样的一行规则,咱们写起来的时候很顺畅:先找 id
为 ul
的元素,再找里面的 li
元素。
可是实际上 CSS 解析器是从右往左的,它会先查找全部 li
元素,而且逐个确认这个 li
元素的父元素的 id
是否是 ul
,这就坑死了。
因此像通配符 * { padding: 0; margin: 0 }
这种,小伙伴们就应该减小设置,要否则页面的元素越多遍历匹配越久。
总结一下:
*
等。span
替换为 .span
。#ul li a
。返回目录
为了不 HTML 解析完毕,可是 CSS 没有解析完毕,从而致使页面直接 “裸奔” 在用户面前的问题,浏览器在处理 CSS 规则树的时候,不会渲染任何已处理的内容。
因此不少时候,咱们会让网页尽早处理 CSS,即在 head
标签中启用 link
或者启用 CDN 实现静态资源加载速度的优化。
返回目录
在上面的加载过程当中咱们并无提到 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
。
返回目录
当使用 JS 去操做 DOM 的时候,其实是 JS 引擎和渲染引擎之间的沟通,这个沟通的过程要开销的。
每操做一次 DOM 就收费一次,多了页面就卡起来咯。
同时,操做 DOM 的时候修改了尺寸等元素,还会引发回流和重绘。
layout
)。当元素的尺寸、结构或者触发某些属性时,浏览器会从新渲染页面,称为回流。此时,浏览器须要从新通过计算,计算后还须要从新页面布局,所以是较重的操做。什么操做触发回流?
border
、margin
、padding
、width
、height
)resize
)什么操做触发重绘?
background
、color
)visibility
)background-image
)咱们仔细看这张图,能够看到重排(Layout
)会致使 Render Tree
重构,进而触发重绘(Painting
):
所以,咱们操做 DOM 的时候,能够这么优化:
visibility
替换 display
table
布局。对于 Render Tree
的计算一般只须要遍历一次就能够完成,可是 table
布局须要计算屡次,一般要花 3 倍于等同元素的时间,所以要避免。width
、height
等会触发回流的操做。返回目录
preload
提供了一种声明式的命令,让浏览器提早加载指定资源(加载后并不执行),在须要执行的时候再执行。
提供的好处主要是:
document
的 onload
事件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
标签。
区分 preload
和 prefetch
:
preload
:告诉浏览器页面一定须要的资源,浏览器必定会加载这些资源。prefetch
:告诉浏览器页面可能须要的资源,浏览器不必定会加载这些资源。固然,开发中须要注意:
preload
preload
和 prefetch
preload
加载跨域资源返回目录
返回目录
懒加载实现思路:
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
返回目录
无限滚动在移动端很常见,可是可见区域渲染并不常见,主要是由于 IOS 上 UIWebView 的 onscroll
并不能实时触发。
实现可见区域渲染的思路:
startIndex
endIndex
startIndex
对应的数据在整个列表中的偏移位置 startOffset
,并设置到列表上代码实现:略。
返回目录
Performance
Page Speed
自动化工具 Lighthouse
npm i lighthouse -g
、lighthouse https://www.baidu.com
LightHouse
的 Audits
面板返回目录
本篇参考文献有 31 篇。
返回目录
2019 年文章:
2018 年文章:
2017 年文章:
返回目录
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的做品创做。<br/>本许可协议受权以外的使用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处得到。