性能优化是把双刃剑,有好的一面也有坏的一面。好的一面就是能提高网站性能,坏的一面就是配置麻烦,或者要遵照的规则太多。而且某些性能优化规则并不适用全部场景,须要谨慎使用,请读者带着批判性的眼光来阅读本文。css
一个完整的 HTTP 请求须要经历 DNS 查找,TCP 握手,浏览器发出 HTTP 请求,服务器接收请求,服务器处理请求并发回响应,浏览器接收响应等过程。接下来看一个具体的例子帮助理解 HTTP :前端
这是一个 HTTP 请求,请求的文件大小为 28.4KB。node
名词解释:webpack
从这个例子能够看出,真正下载数据的时间占比为 13.05 / 204.16 = 6.39%
,文件越小,这个比例越小,文件越大,比例就越高。这就是为何要建议将多个小文件合并为一个大文件,从而减小 HTTP 请求次数的缘由。git
HTTP2 相比 HTTP1.1 有以下几个优势:github
服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,由于 HTTP2 是基于帧的协议,每一个帧都有表示帧长度的字段。web
HTTP1.1 若是要同时发起多个请求,就得创建多个 TCP 链接,由于一个 TCP 链接同时只能处理一个 HTTP1.1 的请求。ajax
在 HTTP2 上,多个请求能够共用一个 TCP 链接,这称为多路复用。同一个请求和响应用一个流来表示,并有惟一的流 ID 来标识。多个请求和响应在 TCP 链接中能够乱序发送,到达目的地后再经过流 ID 从新组建。算法
HTTP2 提供了首部压缩功能。chrome
例若有以下两个请求:
:authority: unpkg.zhimg.com :method: GET :path: /za-js-sdk@2.16.0/dist/zap.js :scheme: https accept: */* accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: no-cache pragma: no-cache referer: https://www.zhihu.com/ sec-fetch-dest: script sec-fetch-mode: no-cors sec-fetch-site: cross-site user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 :authority: zz.bdstatic.com :method: GET :path: /linksubmit/push.js :scheme: https accept: */* accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: no-cache pragma: no-cache referer: https://www.zhihu.com/ sec-fetch-dest: script sec-fetch-mode: no-cors sec-fetch-site: cross-site user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
从上面两个请求能够看出来,有不少数据都是重复的。若是能够把相同的首部存储起来,仅发送它们之间不一样的部分,就能够节省很多的流量,加快请求的时间。
HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储以前发送的键-值对,对于相同的数据,再也不经过每次请求和响应发送。
下面再来看一个简化的例子,假设客户端按顺序发送以下请求首部:
Header1:foo Header2:bar Header3:bat
当客户端发送请求时,它会根据首部值建立一张表:
索引 | 首部名称 | 值 |
---|---|---|
62 | Header1 | foo |
63 | Header2 | bar |
64 | Header3 | bat |
若是服务器收到了请求,它会照样建立一张表。当客户端发送下一个请求的时候,若是首部相同,它能够直接发送这样的首部块:
62 63 64
服务器会查找先前创建的表格,并把这些数字还原成索引对应的完整首部。
HTTP2 能够对比较紧急的请求设置一个较高的优先级,服务器在收到这样的请求后,能够优先处理。
因为一个 TCP 链接流量带宽(根据客户端到服务器的网络带宽而定)是固定的,当有多个请求并发时,一个请求占的流量多,另外一个请求占的流量就会少。流量控制能够对不一样的流的流量进行精确控制。
HTTP2 新增的一个强大的新功能,就是服务器能够对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还能够额外向客户端推送资源,而无需客户端明确地请求。
例如当浏览器请求一个网站时,除了返回 HTML 页面外,服务器还能够根据 HTML 页面中的资源的 URL,来提早推送资源。
如今有不少网站已经开始使用 HTTP2 了,例如知乎:
其中 h2 是指 HTTP2 协议,http/1.1 则是指 HTTP1.1 协议。
客户端渲染: 获取 HTML 文件,根据须要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。
服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。
下面我用 Vue SSR 作示例,简单的描述一下 SSR 过程。
<div id="app"></div>
的 HTML 文件。new Vue()
开始实例化并渲染页面。new Vue()
开始实例化并接管页面。从上述两个过程当中能够看出,区别就在于第二步。客户端渲染的网站会直接返回 HTML 文件,而服务端渲染的网站则会渲染完页面再返回这个 HTML 文件。
这样作的好处是什么?是更快的内容到达时间 (time-to-content)。
假设你的网站须要加载完 abcd 四个文件才能渲染完毕。而且每一个文件大小为 1 M。
这样一算:客户端渲染的网站须要加载 4 个文件和 HTML 文件才能完成首页渲染,总计大小为 4M(忽略 HTML 文件大小)。而服务端渲染的网站只须要加载一个渲染完毕的 HTML 文件就能完成首页渲染,总计大小为已经渲染完毕的 HTML 文件(这种文件不会太大,通常为几百K,个人我的博客网站(SSR)加载的 HTML 文件为 400K)。这就是服务端渲染更快的缘由。
内容分发网络(CDN)是一组分布在多个不一样地理位置的 Web 服务器。咱们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。
当用户访问一个网站时,若是没有 CDN,过程是这样的:
若是用户访问的网站部署了 CDN,过程是这样的:
全部放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染(CSS 不会阻塞 DOM 解析)。若是这些 CSS 和 JS 须要加载和解析好久的话,那么页面就空白了。因此 JS 文件要放在底部,等 HTML 解析完了再加载 JS 文件。
那为何 CSS 文件还要放在头部呢?
由于先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了不这种状况发生,就要将 CSS 文件放在头部了。
另外,JS 文件也不是不能够放在头部,只要给 script 标签加上 defer 属性就能够了,异步下载,延迟执行。
字体图标就是将图标制做成一个字体,使用时就跟字体同样,能够设置属性,例如 font-size、color 等等,很是方便。而且字体图标是矢量图,不会失真。还有一个优势是生成的文件特别小。
使用 fontmin-webpack 插件对字体文件进行压缩。
为了不用户每次访问网站都得请求文件,咱们能够经过添加 Expires 或 max-age 来控制这一行为。Expires 设置了一个时间,只要在这个时间以前,浏览器都不会请求文件,而是直接使用缓存。而 max-age 是一个相对时间,建议使用 max-age 代替 Expires 。
不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器从新请求文件?
能够经过更新页面中引用的资源连接地址,让浏览器主动放弃缓存,加载新资源。
具体作法是把资源地址 URL 的修改与文件内容关联起来,也就是说,只有文件内容变化,才会致使相应 URL 的变动,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?咱们会很天然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种能够精确到单个文件粒度的缓存控制依据了。
压缩文件能够减小文件下载时间,让用户体验性更好。
得益于 webpack 和 node 的发展,如今压缩文件已经很是方便了。
在 webpack 可使用以下插件进行压缩:
其实,咱们还能够作得更好。那就是使用 gzip 压缩。能够经过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。固然,服务器也得支持这一功能。
gzip 是目前最流行和最有效的压缩方法。举个例子,我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB,使用 gzip 压缩后只有 573KB,体积减小了将近 60%。
附上 webpack 和 node 配置 gzip 的使用方法。
下载插件
npm install compression-webpack-plugin --save-dev npm install compression
webpack 配置
const CompressionPlugin = require('compression-webpack-plugin'); module.exports = { plugins: [new CompressionPlugin()], }
node 配置
const compression = require('compression') // 在其余中间件前使用 app.use(compression())
在页面中,先不给图片设置路径,只有当图片出如今浏览器的可视区域时,才去加载真正的图片,这就是延迟加载。对于图片不少的网站来讲,一次性加载所有图片,会对用户体验形成很大的影响,因此须要使用图片延迟加载。
首先能够将图片这样设置,在页面不可见时图片不会加载:
<img data-src="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">
等页面可见时,使用 JS 加载图片:
const img = document.querySelector('img') img.src = img.dataset.src
这样图片就加载出来了。
响应式图片的优势是浏览器可以根据屏幕大小自动加载合适的图片。
经过 picture
实现
<picture> <source srcset="banner_w1000.jpg" media="(min-width: 801px)"> <source srcset="banner_w800.jpg" media="(max-width: 800px)"> <img src="banner_w800.jpg" alt=""> </picture>
经过 @media
实现
@media (min-width: 769px) { .bg { background-image: url(bg1080.jpg); } } @media (max-width: 768px) { .bg { background-image: url(bg768.jpg); } }
例如,你有一个 1920 * 1080 大小的图片,用缩略图的方式展现给用户,而且当用户鼠标悬停在上面时才展现全图。若是用户从未真正将鼠标悬停在缩略图上,则浪费了下载图片的时间。
因此,咱们能够用两张图片来实行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图。还有一种办法,即对大图进行延迟加载,在全部元素都加载完成后手动更改大图的 src 进行下载。
例如 JPG 格式的图片,100% 的质量和 90% 质量的一般看不出来区别,尤为是用来当背景图的时候。我常常用 PS 切背景图时, 将图片切成 JPG 格式,而且将它压缩到 60% 的质量,基本上看不出来区别。
压缩方法有两种,一是经过 webpack 插件 image-webpack-loader
,二是经过在线网站进行压缩。
如下附上 webpack 插件 image-webpack-loader
的用法。
npm i -D image-webpack-loader
webpack 配置
{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use:[ { loader: 'url-loader', options: { limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/ name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, /*对图片进行压缩*/ { loader: 'image-webpack-loader', options: { bypassOnDebug: true, } } ] }
有不少图片使用 CSS 效果(渐变、阴影等)就能画出来,这种状况选择 CSS3 效果更好。由于代码大小一般是图片大小的几分之一甚至几十分之一。
WebP 的优点体如今它具备更优的图像数据压缩算法,能带来更小的图片体积,并且拥有肉眼识别无差别的图像质量;同时具有了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都至关优秀、稳定和统一。
参考资料:
https://www.zhihu.com/questio...
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式其实是先把你的代码在一些逻辑断点处分离开,而后在一些代码块中完成某些操做后,当即引用或即将引用另一些新的代码块。这样加快了应用的初始加载速度,减轻了它的整体体积,由于某些代码块可能永远不会被加载。
经过配置 output 的 filename 属性能够实现这个需求。filename 属性的值选项中有一个 [contenthash],它将根据文件内容建立出惟一 hash。当文件内容发生变化时,[contenthash] 也会发生变化。
output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js', path: path.resolve(__dirname, '../dist'), },
因为引入的第三方库通常都比较稳定,不会常常改变。因此将它们单独提取出来,做为长期缓存是一个更好的选择。这里须要使用 webpack4 的 splitChunk 插件 cacheGroups 选项。
optimization: { runtimeChunk: { name: 'manifest' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。 }, splitChunks: { cacheGroups: { vendor: { name: 'chunk-vendors', test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' }, common: { name: 'chunk-common', minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } }, } },
Babel 转化后的代码想要实现和原来代码同样的功能须要借助一些帮助函数,好比:
class Person {}
会被转换为:
"use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function Person() { _classCallCheck(this, Person); };
这里 _classCallCheck
就是一个 helper
函数,若是在不少文件里都声明了类,那么就会产生不少个这样的 helper
函数。
这里的 @babel/runtime
包就声明了全部须要用到的帮助函数,而 @babel/plugin-transform-runtime
的做用就是将全部须要 helper
函数的文件,从 @babel/runtime包
引进来:
"use strict"; var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck"); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var Person = function Person() { (0, _classCallCheck3.default)(this, Person); };
这里就没有再编译出 helper
函数 classCallCheck
了,而是直接引用了 @babel/runtime
中的 helpers/classCallCheck
。
安装
npm i -D @babel/plugin-transform-runtime @babel/runtime
使用 在 .babelrc
文件中
"plugins": [ "@babel/plugin-transform-runtime" ]
**
**
浏览器渲染过程
重排
当改变 DOM 元素位置或大小时,会致使浏览器从新生成渲染树,这个过程叫重排。
重绘
当从新生成渲染树后,就要将渲染树每一个节点绘制到屏幕,这个过程叫重绘。不是全部的动做都会致使重排,例如改变字体颜色,只会致使重绘。记住,重排会致使重绘,重绘不会致使重排 。
重排和重绘这两个操做都是很是昂贵的,由于 JavaScript 引擎线程与 GUI 渲染线程是互斥,它们同时只能一个在工做。
什么操做会致使重排?
如何减小重排重绘?
事件委托利用了事件冒泡,只指定一个事件处理程序,就能够管理某一类型的全部事件。全部用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托能够节省内存。
<ul> <li>苹果</li> <li>香蕉</li> <li>凤梨</li> </ul> // good document.querySelector('ul').onclick = (event) => { const target = event.target if (target.nodeName === 'LI') { console.log(target.innerHTML) } } // bad document.querySelectorAll('li').forEach((e) => { e.onclick = function() { console.log(this.innerHTML) } })
一个编写良好的计算机程序经常具备良好的局部性,它们倾向于引用最近引用过的数据项附近的数据项,或者最近引用过的数据项自己,这种倾向性,被称为局部性原理。有良好局部性的程序比局部性差的程序运行得更快。
局部性一般有两种不一样的形式:
时间局部性示例
function sum(arry) { let i, sum = 0 let len = arry.length for (i = 0; i < len; i++) { sum += arry[i] } return sum }
在这个例子中,变量sum在每次循环迭代中被引用一次,所以,对于sum来讲,具备良好的时间局部性
空间局部性示例
具备良好空间局部性的程序
// 二维数组 function sum1(arry, rows, cols) { let i, j, sum = 0 for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { sum += arry[i][j] } } return sum }
空间局部性差的程序
// 二维数组 function sum2(arry, rows, cols) { let i, j, sum = 0 for (j = 0; j < cols; j++) { for (i = 0; i < rows; i++) { sum += arry[i][j] } } return sum }
看一下上面的两个空间局部性示例,像示例中从每行开始按顺序访问数组每一个元素的方式,称为具备步长为1的引用模式。若是在数组中,每隔k个元素进行访问,就称为步长为k的引用模式。通常而言,随着步长的增长,空间局部性降低。
这两个例子有什么区别?区别在于第一个示例是按行扫描数组,每扫描完一行再去扫下一行;第二个示例是按列来扫描数组,扫完一行中的一个元素,立刻就去扫下一行中的同一列元素。
数组在内存中是按照行顺序来存放的,结果就是逐行扫描数组的示例获得了步长为 1 引用模式,具备良好的空间局部性;而另外一个示例步长为 rows,空间局部性极差。
运行环境:
对一个长度为9000的二维数组(子数组长度也为9000)进行10次空间局部性测试,时间(毫秒)取平均值,结果以下:
所用示例为上述两个空间局部性示例
步长为 1 | 步长为 9000 |
---|---|
124 | 2316 |
从以上测试结果来看,步长为 1 的数组执行时间比步长为 9000 的数组快了一个数量级。
总结:
当判断条件数量愈来愈多时,越倾向于使用 switch 而不是 if-else。
if (color == 'blue') { } else if (color == 'yellow') { } else if (color == 'white') { } else if (color == 'black') { } else if (color == 'green') { } else if (color == 'orange') { } else if (color == 'pink') { } switch (color) { case 'blue': break case 'yellow': break case 'white': break case 'black': break case 'green': break case 'orange': break case 'pink': break }
像以上这种状况,使用 switch 是最好的。假设 color 的值为 pink,则 if-else 语句要进行 7 次判断,switch 只须要进行一次判断。从可读性来讲,switch 语句也更好。
从使用时机来讲,当条件值大于两个的时候,使用 switch 更好。不过 if-else 也有 switch 没法作到的事情,例若有多个判断条件的状况下,没法使用 switch。
当条件语句特别多时,使用 switch 和 if-else 不是最佳的选择,这时不妨试一下查找表。查找表可使用数组和对象来构建。
switch (index) { case '0': return result0 case '1': return result1 case '2': return result2 case '3': return result3 case '4': return result4 case '5': return result5 case '6': return result6 case '7': return result7 case '8': return result8 case '9': return result9 case '10': return result10 case '11': return result11 }
能够将这个 switch 语句转换为查找表
const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11] return results[index]
若是条件语句不是数值而是字符串,能够用对象来创建查找表
const map = { red: result0, green: result1, } return map[color]
60fps 与设备刷新率
目前大多数设备的屏幕刷新率为 60 次/秒。所以,若是在页面中有一个动画或渐变效果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也须要跟设备屏幕的刷新率保持一致。
其中每一个帧的预算时间仅比 16 毫秒多一点 (1 秒/ 60 = 16.66 毫秒)。但实际上,浏览器有整理工做要作,所以您的全部工做须要在 10 毫秒内完成。若是没法符合此预算,帧率将降低,而且内容会在屏幕上抖动。此现象一般称为卡顿,会对用户体验产生负面影响。
假如你用 JavaScript 修改了 DOM,并触发样式修改,经历重排重绘最后画到屏幕上。若是这其中任意一项的执行时间过长,都会致使渲染这一帧的时间过长,平均帧率就会降低。假设这一帧花了 50 ms,那么此时的帧率为 1s / 50ms = 20fps,页面看起来就像卡顿了同样。
对于一些长时间运行的 JavaScript,咱们可使用定时器进行切分,延迟执行。
for (let i = 0, len = arry.length; i < len; i++) { process(arry[i]) }
假设上面的循环结构因为 process() 复杂度太高或数组元素太多,甚至二者都有,能够尝试一下切分。
const todo = arry.concat() setTimeout(function() { process(todo.shift()) if (todo.length) { setTimeout(arguments.callee, 25) } else { callback(arry) } }, 25)
若是有兴趣了解更多,能够查看一下高性能JavaScript第 6 章和高效前端:Web高效编程与优化实践第 3 章。
从第 16 点咱们能够知道,大多数设备屏幕刷新率为 60 次/秒,也就是说每一帧的平均时间为 16.66 毫秒。在使用 JavaScript 实现动画效果的时候,最好的状况就是每次代码都是在帧的开头开始执行。而保证 JavaScript 在帧开始时运行的惟一方式是使用 requestAnimationFrame
。
/** * If run as a requestAnimationFrame callback, this * will be run at the start of the frame. */ function updateScreen(time) { // Make visual updates here. } requestAnimationFrame(updateScreen);
若是采起 setTimeout
或 setInterval
来实现动画的话,回调函数将在帧中的某个时点运行,可能恰好在末尾,而这可能常常会使咱们丢失帧,致使卡顿。
Web Worker 使用其余工做线程从而独立于主线程以外,它能够执行任务而不干扰用户界面。一个 worker 能够将消息发送到建立它的 JavaScript 代码, 经过将消息发送到该代码指定的事件处理程序(反之亦然)。
Web Worker 适用于那些处理纯数据,或者与浏览器 UI 无关的长时间运行脚本。
建立一个新的 worker 很简单,指定一个脚本的 URI 来执行 worker 线程(main.js):
var myWorker = new Worker('worker.js'); // 你能够经过postMessage() 方法和onmessage事件向worker发送消息。 first.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); } second.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); }
在 worker 中接收到消息后,咱们能够写一个事件处理函数代码做为响应(worker.js):
onmessage = function(e) { console.log('Message received from main script'); var workerResult = 'Result: ' + (e.data[0] * e.data[1]); console.log('Posting message back to main script'); postMessage(workerResult); }
onmessage处理函数在接收到消息后立刻执行,代码中消息自己做为事件的data属性进行使用。这里咱们简单的对这2个数字做乘法处理并再次使用postMessage()方法,将结果回传给主线程。
回到主线程,咱们再次使用onmessage以响应worker回传的消息:
myWorker.onmessage = function(e) { result.textContent = e.data; console.log('Message received from worker'); }
在这里咱们获取消息事件的data,而且将它设置为result的textContent,因此用户能够直接看到运算的结果。
不过在worker内,不能直接操做DOM节点,也不能使用window对象的默认方法和属性。然而你可使用大量window对象之下的东西,包括WebSockets,IndexedDB以及FireFox OS专用的Data Store API等数据存储机制。
JavaScript 中的数字都使用 IEEE-754 标准以 64 位格式存储。可是在位操做中,数字被转换为有符号的 32 位格式。即便须要转换,位操做也比其余数学运算和布尔操做快得多。
因为偶数的最低位为 0,奇数为 1,因此取模运算能够用位操做来代替。
if (value % 2) { // 奇数 } else { // 偶数 } // 位操做 if (value & 1) { // 奇数 } else { // 偶数 }
~~10.12 // 10 ~~10 // 10 ~~'1.5' // 1 ~~undefined // 0 ~~null // 0
const a = 1 const b = 2 const c = 4 const options = a | b | c
经过定义这些选项,能够用按位与操做来判断 a/b/c 是否在 options 中。
// 选项 b 是否在选项中 if (b & options) { ... }
不管你的 JavaScript 代码如何优化,都比不上原生方法。由于原生方法是用低级语言写的(C/C++),而且被编译成机器码,成为浏览器的一部分。当原生方法可用时,尽可能使用它们,特别是数学运算和 DOM 操做。
看个示例
#block .text p { color: red; }
内联 > ID选择器 > 类选择器 > 标签选择器
根据以上两个信息能够得出结论。
最后要说一句,据我查找的资料所得,CSS 选择器没有优化的必要,由于最慢和慢快的选择器性能差异很是小。
在早期的 CSS 布局方式中咱们能对元素实行绝对定位、相对定位或浮动定位。而如今,咱们有了新的布局方式 flexbox,它比起早期的布局方式来讲有个优点,那就是性能比较好。
下面的截图显示了在 1300 个框上使用浮动的布局开销:
而后咱们用 flexbox 来重现这个例子:
如今,对于相同数量的元素和相同的视觉外观,布局的时间要少得多(本例中为分别 3.5 毫秒和 14 毫秒)。
不过 flexbox 兼容性仍是有点问题,不是全部浏览器都支持它,因此要谨慎使用。
各浏览器兼容性:
在 CSS 中,transforms 和 opacity 这两个属性更改不会触发重排与重绘,它们是能够由合成器(composite)单独处理的属性。
参考资料:
性能优化主要分为两类:
上述 23 条建议中,属于加载时优化的是前面 10 条建议,属于运行时优化的是后面 13 条建议。一般来讲,没有必要 23 条性能优化规则都用上,根据网站用户群体来作针对性的调整是最好的,节省精力,节省时间。
在解决问题以前,得先找出问题,不然无从下手。因此在作性能优化以前,最好先调查一下网站的加载性能和运行性能。
一个网站加载性能如何主要看白屏时间和首屏时间。
将如下脚本放在 </head>
前面就能获取白屏时间。
<script> new Date() - performance.timing.navigationStart </script>
在 window.onload
事件里执行 new Date() \- performance.timing.navigationStart
便可获取首屏时间。
配合 chrome 的开发者工具,咱们能够查看网站在运行时的性能。