原文刊登在github,若是掘金有图片加载不出的,你们也能够直接看原版 github地址javascript
在互联网网站百花齐放的今天,网站响应速度是用户体验的第一要素,其重要性不言而喻,这里有几个关于响应时间的重要条件:css
用户在浏览网页时,不会注意到少于0.1秒的延迟;html
少于1秒的延迟不会中断用户的正常思惟, 可是一些延迟会被用户注意到;前端
延迟时间少于10秒,用户会继续等待响应;java
延迟时间超过10秒后,用户将会放弃并开始其余操做;node
所以你们都开始注重性能优化,不少厂商都开始作一些性能优化。比较有名的是雅虎军规,不过随着浏览器和协议等的发展,有一些已经逐渐被淘汰了。所以建议你们以历史的目光看待它。好比.尽可能减小HTTP请求数这一条,在HTTP2协议下就无论用了,由于HTTP2实现了HTTP复用,HTTP请求变少,反而下降性能。所以必定要结合历史环境看待具体的优化原则和手段,不然会拔苗助长。react
雅虎军规中文版: http://www.cnblogs.com/xianyulaodi/p/5755079.htmllinux
随着移动互联网的高速发展,移动终端的数量正在以指数级增加,不少厂商对于移动端体验都开始重视起来了。好比Google Chrome 的工程师 Alex就提出了Progressive Web App(如下简称PWA),用来提升移动端web的性能。PWA的核心是Service Worker, 经过它可使得在JS主线程以外,程序员经过编程的方式控制网络请求,结合浏览器自己的缓存,每每能够达到很是棒的用户体验。PWA提出了许多相似Native的“功能”- 好比离线加载和桌面快捷方式,使得移动端web体验更加友好。另外加上web端自己的特性-好比快速迭代,可索引(你能够经过搜索引擎搜索,而native app 则不行)等,使得更多的人投入到给web端用户提供更佳的用户体验的PWA中去。Google在更早的时候,提出了AMP。 2017年Google dev上海站就宣传了PWA 和 AMP,而且经过一张动图形象地展现了二者(PWA的P和A翻过来,而后W上下翻转就是AMP,反之亦然)。AMP是一种面向手机端的轻量级的web展示,经过将重量级元素从新实现等方式提升了手机端性能。 另外诸如使用asm.js 使得代码更容易编译成机器指令,也是性能优化的一环。若是你仔细查看应用执行的profile的时候,你会发现js代码compile的时间会好久,尤为你写了不少无用js代码,或者不必第一时间执行的代码的时候,这种影响更加大。js代码最终也是编译成二进制给机器执行,而js是动态语言,也就是说js代码是执行到哪编译到哪,这是和java这样的静态语言的一个很大的差异。V8已经对这部分作了至关大的优化,通常状况下咱们没必要担忧,一般来说这也不会成为性能瓶颈,由于这些时间和网络IO的时间根本不是一个数量级。可是在特定场合,提早编译成更容易解释执行的代码,可能就会派上用场。git
在我刚刚接触前端的时候,常常看到这样的性能优化例子:程序员
// bad
for(var i = 0;i < data.length;i++){
// do something...
}
// good
for(var i = 0,len = data.length;i < len;i++){
// do something...
}
复制代码
理由是上面的会每次去计算data.length。我的上面的优化很是好笑,且不说实际运行状况怎样。就算下面的性能比上面的好,我以为这样的性能优化应该交给编译器来作,不该该交给上层业务去作,这样作反而丧失了可理解性,你们很明显的看出上面的更容易理解,不是吗?
过早的优化,会让人陷入心理误区。这种心理误区就是典型的手中有锤子,到处都是钉子。
还有一点就是若是过早优化,每每会一叶障目。性能优化要遵照木桶原理,即影响系统性能的永远是系统的性能短板。若是过早优化,每每会头痛医脚,忙手忙脚却毫无收效。
让咱们回忆一下浏览器从加载url开始到页面展现出来,通过了哪些步骤:
tips: 在chrome浏览器中, 能够输入 chrome://dns/ 查看chrome缓存的dns记录
浏览器调用网络模块。 网络模块和目标IP 创建TCP链接,途中通过三次握手。
浏览器发送http请求,请求格式以下:
header
(空行)
body
复制代码
请求到达目标机器,并经过端口与目标web server 创建链接。
web server 获取到请求流,对请求流进行解析,而后通过一些列处理,可能会查询数据库等, 最终返回响应流到前端。
浏览器下载文档(content download),并对文档进行解析。解析的过程以下所示:
知道了浏览器加载网页的步骤,咱们就能够从上面每个环节采起”相对合适“的策略优化加载速度。 好比上面第二步骤会进行dns查找,那么dns查找是须要时间的,若是提早将dns解析并进行缓存,就能够减小这部分性能损失。在好比创建TCP链接以后,保持长链接的状况下能够串行发送请求。熟悉异步的朋友确定知道串行的损耗是很大的,它的加载时间取决于资源加载时间的和。而采起并行的方式是全部加载时间中最长的。这个时候咱们能够考虑减小http 请求或者使用支持并行方式的协议(好比HTT2协议)。若是你们熟悉浏览器的原理或者仔细观察网络加载图的化,会发现同时加载的资源有一个上限(根据浏览器不一样而不一样),这是浏览器对于单个域名最大创建链接个数的限制,因此能够考虑增长多个domain来进行优化。相似的还有不少,留给你们思考。可是总结下来只有两点,一是加载优化,即提升资源加载的速度。第二个是渲染优化,即资源拿到以后到解析完成的阶段的优化。
通过上面简单的讲解,我想你们对浏览器加载HTML开始到页面呈现出来,有了一个大概的认识,后面我会更详细地讲解这个过程。有一个名词叫关键路径(Critical Path),它指的是从浏览器收到 HTML、CSS 和 JavaScript 字节到对其进行必需的处理,从而将它们转变成渲染的像素。这一过程当中有一些中间步骤,优化性能其实就是了解这些步骤中发生了什么。记住关键路径上的资源有HTML,CSS,JavaScript,其中并不包括图片,虽然图片在咱们的应用中很是广泛,可是图片并不会阻止用户的交互,所以不计算到关键路径,关于图片的优化我会在下面的小节中重点介绍。
为了让你们有更清晰地认识,我将上面浏览器加载网站步骤中的第七步中的CSSOM和DOM以及render tree的构建过程,更详细地讲解一下。
浏览器请求服务端的HTML文件,服务端响应字节流给浏览器。浏览器接受到HTML而后根据指定的编码格式进行解码。完成以后会分析HTML内容,将HTML分红一个个token,而后根据不一样token生成不一样的DOM,最后根据HTML中的层级结构生成DOM树。
其中要注意的是,若是碰到CSS标签和JavaScript标签(不是async或者defer的js脚本)会暂停渲染,等到资源加载完毕,继续渲染。若是加载了CSS文件(内敛样式同理),会在加载完成CSS以后生成CSSOM。CSSOM的生成过程相似,也是将CSS分红一个个token,而后根据不一样token生成CSSOM,CSSOM是用来控制DOM的样式的。最后将DOM和CSSOM合成render tree。
CSS 是阻塞渲染的资源。须要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
为弄清每一个对象在网页上的确切大小和位置,浏览器从渲染树的根节点开始进行遍历,根据盒模型和CSS计算规则生成计算样式(chrome中叫computed style),最后调用绘制线程将DOM绘制到页面上。所以优化上面每个步骤都很是重要。如今咱们有了清晰的认识,关键资源HTML是一切的起点,没有HTML后面就没有意义。CSS应该尽快下载并解析,一般咱们将css放在head里面优先加载执行,就像app shell的概念同样。咱们应该优先给用户呈现最小子集,而后慢慢显示其余的内容,就好像PJPEG(progressive jpeg)同样。以下图是一个渐进式渲染的一个例子(图片来自developers.google.com):
通过上面的分析,咱们知道了关键路径。咱们能够借助chrome开发工具查看瀑布图,分析网站的关键路径,分析加载缓慢,影响网站速度的瓶颈点。
也可使用一些工具检测,好比前面提到的web performance test,也能够尝试下Lighthouse。
在后面的小节,我会介绍performance api,你们能够在前端埋点,而后分析网站的性能指标,这也是对其余分析手法的一个重要补充。
用户从打开页面开始到有页面开始呈现为止。白屏时间长是没法忍受的,所以有了不少的缩短白屏时间的方法。 好比减小首屏加载内容,首屏内容渐出等。白屏的测量方法最古老的方法是这样的:
<head>
<script> var t = new Date().getTime(); </script>
<link src="">
<link src="">
<link src="">
<script> tNow = new Date().getTime() - t; </script>
</head>
复制代码
可是上面这种只能测量首屏有html内容的状况,好比像react这样客户端渲染的方式就不行了。若是采用客户端渲染的方式,就须要在首屏接口返回, 并渲染页面的地方打点记录。
经过相似的方法咱们还能够查看图片等其余资源的加载时间,以图片为例:
<img src="xx" id="test" />
<script> var startLoad = new Date().getTime() document.getElementById('test').addEventListener('load', function(){ var duration = new Date().getTime() - startLoad(); }, false) </script>
复制代码
经过这种方法未免太麻烦,还在浏览器performance api 提供了不少有用的接口,方便你们计算各类性能指标。下面performance api 会详细讲解。
咱们所说的首屏时间,就是指用户在没有滚动时候看到的内容渲染完成而且能够交互的时间。至于加载时间,则是整个页面滚动到底部,全部内容加载完毕并可交互的时间。用户能够进行正常的事件输入交互操做。
firstscreenready - navigationStart
复制代码
这个时间就是用户实际感知的网站快慢的时间。firstscreenready 没有这个 performance api, 并且不一样的渲染手段(服务端渲染和客户端渲染计算方式也不一样),不能一律而论。具体计算方案,这边文章写得挺详细的。首屏时间计算
一般网页以两个事件的触发时间来肯定页面的加载时间.
DOMContentLoaded 事件,表示直接书写在HTML页面中的内容但不包括外部资源被加载完成的时间,其中外部资源指的是css、js、图片、flash等须要产生额外HTTP请求的内容。
onload 事件,表示连同外部资源被加载完成的时间。
domComplete - domLoading
复制代码
上面介绍了古老的方法测量关键指标,主要原理就是基于浏览器从上到下加载的原理。只是上面的方法比较麻烦,不适合实际项目中使用。 实际项目中仍是采用打点的方式。 即在关键的地方埋点,而后根据须要将打点信息进行计算获得咱们但愿看到的各项指标,performance api 就是这样一个东西。
The Performance interface provides access to performance-related information for the current page. It's part of the High Resolution Time API, but is enhanced by the Performance Timeline API, the Navigation Timing API, the User Timing API, and the Resource Timing API.
------ 摘自MDN
在浏览器console中输入performance.timing
返回的各字节跟下面的performance流程的各状态一一对应,并返回时间。这个和js中直接new Date().getTime()的时间是不同的。 这个时间和真实时间没有关系,并且perfermance api精确度更高。
有了这个performance api 咱们能够很方便的计算各项性能指标。若是performamce api ”埋的点“不够咱们用,咱们还能够自定义一些咱们关心的指标,好比请求时间(成功和失败分开统计),较长js操做时间,或者比较重要的功能等。总之,只要咱们想要统计的,咱们均可以借助performance api 轻松实现。
performance api 更多介绍请查看 https://developer.mozilla.org/en-US/docs/Web/API/Performance
监控是基于日志的
一个格式良好,内容全面的日志是实现监控的重要条件,能够说基础决定上层建筑。 良好的的日志系统一般有如下几个部分构成:
日志由产生到进入日志系统的过程。 好比rsyslog生成的日志,经过logstash(transport and process your logs, events, or other data)接入到日志系统。这种比较简单,因为是基于原生linux的日志系统,学习使用成本也比较低。公司不一样系统若是须要介入日志系统,只须要将日志写入log目录,经过logstash等采集就能够了。
这部分是日志系统的核心,处理层能够将接入层产生的日志进行分析。过滤日志发送到监控中心(监控系统状态)和存储中心(数据汇总,查询等)
将日志入库,根据业务状况,创建索引。这部分一般还能够接入像elastic search这样的库,提供日志的查询,上面的logstash 就是elastic家族的。
除了上面核心的几层,一般还有其余层完成更为细化的工做。
一般来讲,一个公司的日志有如下几个方面
记录一些关键指标,具体的关键指标能够参阅“浏览器性能指标”一节。
记录后端的服务器错误(500,502等),前端的脚本错误(script error)。
记录硬件资源的使用率,好比内存,网络带宽和硬盘等。
记录业务方比较关心的用户的操做。方便根据用户报的异常,定位问题。
统计日志一般是基于存储日志的内容进行统计。统计日志有点像数据库视图的感受,经过视图屏蔽了数据库的结构信息,将数据库一部份内容透出到用户。用户行为分析等一般都是基于统计日志分析的。有的公司甚至介入了可视化的日志统计系统(好比Kibana)。随着人工智能的崛起,人工智能+日志是一个方向。
除了上面介绍的关键指标记录。咱们一般还比较关心接口的响应速度。这时候咱们能够经过打点的方式记录。
咱们已经产出了日志,有了日志数据源。那么如何消费数据呢? 如今广泛的作法是服务端将收集的日志进行转储,并经过可视化手段(图标等)展现给管理员。还有一种是用户本身消费,即自产自销。用户产生数据,同时本身消费,提供更加的用户体验。 详情查阅 locus 可是性能日志明显不能自产自销,咱们暂时只考虑第一种。
监控平台大公司基本都有本身的系统。好比有赞的Hawk,阿里的SunFire。小公司一般都是使用开源的监控系统或者干脆没有。 我以前的公司就没有什么监控平台,最多只是阿里云提供的监控数据而已。因此我在这一方面作了必定的探索。并开始开发朱雀平台,可是限于精力有限,该计划最后没有最终投入使用,仍是蛮惋惜的。性能监测的本质是基于监测的数据,提供方便的查询和可视化的统计。并对超过临界值(一般还有持续时长限制)发出警告。 上一节介绍了性能监控平台,提到了性能监控平台的两个组成部分,一个是生产者一个是消费者。 这节介绍如何搭建一个监控平台。那么我先来看下总体的架构
为了方便讲解,这里只实现一个最简化的模型,读者能够在此基础上进一步划分子系统,好比接入SSO,存储展现分离等。
客户端一方面上报埋点信息,另外一方面上报轨迹信息。
这一部分主要借助一些手段,好比performance api 将网页相关加载时间信息上报到后端。
performance.getEntriesByType("resource").forEach(function(r) {
console.log(r.name + ": " + r.duration)
})
复制代码
另外一方面对特定的异步请求接口,打点。对用户全部的交互操做打点(点击,hover等)
const startTime = new Date().getTime();
fetch(url)
.then(res => {
const endTime = new Date().getTime();
report(url , 'success', endTime - startTime);
})
.catch(err => {
const endTime = new Date().getTime();
report(url, 'failure', endTime - startTime);
})
复制代码
上传轨迹信息就简单了。若是是页面粒度,直接在页面上报就能够了。若是使用了前端路由,还能够在路由的钩子函数中进行上报。
pageA.js
// 上报轨迹
report('pageA',{userId: '876521', meta: {}})
复制代码
这样咱们就有了数据源了。
服务端已经有了数据,后端须要将数据进行格式化,并输出。
客户端将本身的信息上报到server,由server进行统计汇总,并在合适的时候将处理后的数据下发到客户端,指导客户端的行为(如预加载)。
前面说了客户端上传的信息大概是
{
userId: 876521,
page: "index",
area: "",
age: "",
// 其余群体特征
}
复制代码
咱们称地域,年龄等为群体特征,群体特征对于分析统计有很是重要的意义。
咱们能够对单用户进行汇总,也能够对群体特征进行汇总从而预测客户的行为。
咱们汇总的数据多是:
{
userId: '876521',
pages: {
"index": {
"detail": 0.5,
"return": 0.4,
"personnal-center": 0.1
}
}
}
复制代码
如上是以用户为纬度进行分析。上面的数据表明,若是用户876521在首页(index),那么ta下一步访问详情页(detail)的几率是50%,访问我的中心(personal-center)的几率为10%,退出页面几率为40%。
咱们就能够在可能的状况下,用户停留在首页的时候预加载详情页。
咱们还能够对群体特征进行汇总,
汇总的结果多是:
{
age: 'teen',
pages: {
"index": {
"detail": 0.5,
"return": 0.4,
"personnal-center": 0.1
}
}
}
复制代码
其实和上面差很少,不过这里并非只是用来指导某一个用户,而是能够指导同一个群体特征(这里指同一年龄段)的用户。
客户端会上传性能信息给zhuque server。
zhuque server 在这里主要有两个职责:
这部分一般作起来简单,作好难。 我在这方面经验不够多,就不误导你们了。
警报种类有不少,好比邮件,电话,短信,钉钉等。 咱们只要设置好触发条件,而后写一个定时任务或者在请求级别进行检查,若是知足就触发警报便可。逻辑很是简单。
定时任务对系统的压力较小,可是及时性较低,适合对实时性要求不强的业务。 请求级别检查謉系统压力较大,可是及时性有保障,适合对实时性要求很是高的业务。
要作性能优化,首先要对系统运行的过程有一个完整的理解,而后从各个环节分析,找到系统瓶颈,从而进行优化。在这里我不罗列性能优化的各类手段,而是从前端三层角度逐个描述下性能优化的常见优化方向和手段。若是你们但愿有一个完整的优化清单, 这里有一份比较完整的Front-End-Checklist,对于性能优化,有必定的借鉴意义。另外你也能够访问webpagetest测试你的网站的性能,并针对网站提供的反馈一步步优化你的网站加载速度,这些内容不在本文论述范围。 性能优化一个最重要的原则是:永远呈现必要的内容,咱们能够经过懒加载非首屏资源,或者采用分页的方式将数据”按需加载“。下面讲述一些具体的优化手段。 不少人都知道,前端将应用分为三层,分别是结构层,表现层和行为层。咱们就从三层角度讲一下性能优化的方向。
结构层指的是DOM结构,而DOM结构一般是由HTML结构决定的,所以咱们主要分析下HTML结构的性能优化点。 咱们知道DOM操做是很是昂贵的,这在前面讲述前端发展历史的时候也提到了。如何减小DOM数量,减小DOM操做是优化须要 重点关注的地方。
说到HTML优化,不得不提AMP HTML。 AMP的核心思想是提供移动端更佳的用户体验,。由AMP HTML, AMP JS 和 AMP Cache 三个核心部分组成。
AMP HTML is HTML with some restrictions for reliable performance.
下面是典型的AMP HTML
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<link rel="canonical" href="hello-world.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>Hello World!</body>
</html>
复制代码
能够看出AMP HTML由普通的HTML标签和amp标签组成。amp标签是作什么呢?且听我跟你说,DOM虽然操做比较昂贵,可是不一样的DOM效率也是不意义的。好比渲染一个a标签和渲染一个img或者table时间确定不是同样的。咱们称a标签这样渲染较快的元素为轻元素。称table,img这样的元素为重元素。那么咱们就应该尽可能避免重元素的出现,好比table 能够采用ul li 实现。 img之因此比较慢的缘由是图片下载虽然是异步的,可是会占用网络线程,同时会多发一个请求(浏览器并发请求数是有限制的),所以能够进一步封装称轻元素(好比x-image,组件内部能够延迟发送图片请求,等待主结构渲染完毕再发图片请求)。 能够考虑将其封装为web-component或者其余组件形式(如react组件)
回到刚才AMP HTML, 其实在amp 中 有一个amp-image这样的接口,大概能够根据须要本身实现,上面咱们说的x-image 其实就是实现了amp接口规范的组件。
前面说到了尽量使用轻元素。那么除了使用轻元素,还有一点也很重要,就是减小DOM数量。减小DOM数量的一个重要的途径就是减小冗余标签。好比咱们经过新增长一个元素清除浮动。
<div class="clear"></div>
复制代码
不过目前都是采用伪元素实现了。另外一个途径是减小嵌套层次。不少人都会在<form>
或者<ul>
外边包上<div>
标签,为何加上一个你根本不须要的<div>
标签呢?实际上你彻底能够用CSS selector,实现一样的效果。 重要的是,这种代码我见过不少。
<div class="form">
<form>
...
</form>
</div>
复制代码
彻底能够这样写:
<form class="form">
...
</form>
复制代码
表现层就是咱们一般使用的CSS。CSS通过浏览器的解析会生成CSS TREE,进而和DOM TREE合成 RENDER TREE。有时候一些功能彻底能够经过CSS去实现,没有必要使用javaScript,尤为是动画方面,CSS3增长了transition 和 transform 用来实现动画,开发者甚至能够经过3D加速功能来充分发挥GPU的性能。所以熟练使用CSS,并掌握CSS的优化技巧是必不可少的。CSS 的性能优化一般集中在两方面:
提升加载性能就是减小加载所消耗的时间。简单说就是减少CSS文件的大小,提升页面的加载速度,尽能够的利用http缓存等。代码层面咱们要避免引入不须要的样式,合理运用继承减小代码。
<body>
<div class="list" />
</body>
复制代码
body { color: #999 }
.list {color: #999} // color 其实能够继承body,所以这一行不必
复制代码
其余能够继承的属性有color,font-size,font-family等。
一般来讲这部分和JS等静态资源的优化道理是同样的。只不过目前网站有一个理念是框架优先,即先加载网站的主题框架,这部分一般是静态部分,而后动态加载数据,这样给用户的感受是网站”很快“。而这部分的静态内容,一般能够简单的HTML结构(Nav + footer),加上CSS样式来完成。 这就要求主题框架的CSS优先加载,咱们设置能够将这部分框架样式写到内敛样式中去,可是有的人以为这样不利于代码的维护。
浏览器对不一样的代码执行效率是不一样的,复杂的样式(多层嵌套)也会下降css解析效率,所以能够将复杂的嵌套样式进行转化。
.wrapper .list .item .success {}
// 能够写成以下:
.wrapper .list .item-success {}
复制代码
还有一部分是网站的动画,动画一般来讲要作到16ms之内,以让用户感受到很是流畅。另外咱们还能够经过3D加速来充分应用GPU的性能。 这里引用于江水的一句话:
只有在很是复杂的页面,样式很是多的时候,CSS 的性能瓶颈才会凸显出来,这时候更多要考虑的应该是有没有必要作这么复杂的页面。
行为层指的是用户交互方面的内容,在前端主要经过JavaScript实现。目前JavaScript 规范已经到es2017。 前端印象较为深入的是 ES6(也就是ES2015),由于ES5是2009年发布的,以后过了6年,也就是2015年ES6才正式发布,其中增长了许多激动人心的新特性, 被广大前端所熟悉。甚至曾一度称目前前端状态是536(HTML5,CSS3,ES6),可见其影响力。
绝不夸张的说,目前前端项目绝大多数代码都是javascript。既然js用的这么多,为何不多有人谈js性能优化呢? 一是由于如今工业技术的发展,硬件设备的性能提高,致使前端计算性能一般不认为是一个系统的性能瓶颈。二是随着V8引擎的发布,js执行速度获得了很大的提高。三是由于计算性能是本地CPU和内存的工做,其相对于网路IO根本不是一个数量级,所以人们更多关注的是IO方面的优化。那么为何还要将js性能优化呢?一方面是目前前端会经过node作一些中间层,甚至是后端,所以须要重点关注内存使用状况,这和浏览器是截然不同的。另外一方面是由于前端有时候也会写一个复杂计算,也会有性能问题。 最后一点是咱们是否能够经过JS去优化网络IO的性能呢,好比使用JS API 操做 webWorker 或者使用localStorage缓存。
前端偶尔也会有一些数据比较大的计算。 对于一些复杂运算,一般咱们会将计算结果进行缓存,以供下次使用。前面提到了纯函数的概念,要想使用计算缓存,就要求函数要是纯函数。一个简单的缓存函数代码以下:
// 定义
function memoize(func) {
var cache = {};
var slice = Array.prototype.slice;
return function() {
var args = slice.call(arguments);
if (args in cache)
return cache[args];
else
return (cache[args] = func.apply(this, args));
}
}
// 使用
function cal() {}
const memoizeCal = memoize(cal);
memoizeCal(1) // 计算,并将结果缓存
memoizeCal(1) // 直接返回
复制代码
前面讲了计算方面的优化,它的优化范围是比较小的。由于并非全部系统都会有复杂计算。可是网络IO是全部系统都存在的,并且网络IO是不稳定的。网络IO的 速度和本地计算根本不是一个数量级,好在咱们的浏览器处理网络请求是异步的(固然能够代码控制成同步的)。一种方式就是经过本地缓存,将网络请求结果存放到本地,在下次请求的时候直接读取,不须要重复发送请求。一个简单的实现方法是:
function cachedFetch(url, options) {
const cache = {};
if (cache[url]) return cache[url];
else {
return fetch(url, options).then(res) {
cache[url] = res
return res;
}
}
}
复制代码
固然上面的粗暴实现有不少问题,好比没有缓存失效策略(好比能够采用LRU策略或者经过TTL),可是基本思想是这样的。 这种方式的优势很明显,就是显著减小了系统反馈时间,固然缺点也一样明显。因为使用了缓存,当数据更新的时候,就要考虑缓存更新同步的问题,不然会形成数据不一致,形成很差的用户体验。
数据结构和算法的优化是前端接触比较少的。可是若是碰到计算量比较大的运算,除了运用缓存以外,还要借助必定的数据结构优化和算法优化。 好比如今有50,000条订单数据。
const orders = [{name: 'john', price: 20}, {name: 'john', price: 10}, ....]
复制代码
我须要频繁地查找其中某我的某天的订单信息。 咱们能够采起以下的数据结构:
const mapper = {
'john|2015-09-12': []
}
复制代码
这样咱们查找某我的某天的订单信息速度就会变成O(1),也就是常数时间。你能够理解为索引,由于索引是一种数据结构,那么咱们也可使用其余数据结构和算法适用咱们各自独特的项目。对于算法优化,首先就要求咱们可以识别复杂度,常见的复杂度有O(n) O(logn) O(nlogn) O(n2)。而对于前端,最基本的要识别糟糕的复杂度的代码,好比n三次方或者n阶乘的代码。虽然咱们不须要写出性能很是好的代码,可是也尽可能不要写一些复杂度很高的代码。
经过HTML5的新API webworker,使得开发者能够将计算转交给worker进程,而后经过进程通讯将计算结果回传给主进程。毫无疑问,这种方法对于须要大量计算有着很是明显的优点。
代码摘自Google performance:
var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);
// The main thread is now free to continue working on other things...
dataSortWorker.addEventListener('message', function(evt) {
var sortedData = evt.data;
// Update data on screen...
});
复制代码
因为WebWorker 被作了不少限制,使得它不能访问诸如window,document这样的对象,所以若是你须要使用的话,就不得不寻找别的方法。
一种使用web worker的思路就是分而治之,将大任务切分为若干个小任务,而后将计算结果汇总,咱们一般会借助数组这种数据结构来完成,下面是一个例子:
// 不少小任务组成的数组
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
// 使用更新的api requestAnimationFrame而不是setTimeout能够提升性能
requestAnimationFrame(processTaskList);
function processTaskList(taskStartTime) {
var taskFinishTime;
do {
// Assume the next task is pushed onto a stack.
var nextTask = taskList.pop();
// Process nextTask.
processTask(nextTask);
// Go again if there’s enough time to do the next task.
taskFinishTime = window.performance.now();
} while (taskFinishTime - taskStartTime < 3);
if (taskList.length > 0)
requestAnimationFrame(processTaskList);
}
复制代码
线程安全问题都是由全局变量及静态变量引发的。 若每一个线程中对全局变量、静态变量只有读操做,而无写操做,通常来讲,这个全局变量是线程安全的;如有多个线程同时执行写操做,就须要考虑线程同步,就可能产生线程安全问题。
你们能够没必要太担忧,web worker已经在这方面作了不少努力,例如你没有办法去访问非线程安全的组件或者是 DOM,此外你还须要经过序列化对象来与线程交互特定的数据。所以你们若是想写出线程不安全的代码,还真不是那么容易的。
V8是由Google提出的,它经过将js代码编译成机器码,而非元组码或者解释它们,进而提升性能。V8内部还封装了不少高效的算法,不少开发者都会研究V8的源码来提高本身。 这里有些js优化的实践,详情可看下这篇文章 还有不少其余有趣的研究。
Benedikt Meurer(Tech Lead of JavaScript Execution Optimization in Chrome/V8)本人致力于V8的性能研究,写了不少有深度的文章,而且开源了不少有趣的项目,有兴趣的能够关注一下。
前端中的内存泄漏不是很常见,可是仍是有必要知道一下,最起码可以在出现问题的时候去解决问题。更为低级的语言如C语言,有申请内存malloc和销毁内存free的操做。而在高级语言好比java和js,屏蔽了内存分配和销毁的细节,而后经过GC(垃圾回收器)去清除不须要使用的内存。
只有开发人员本身知道何时应该销毁内存。
好在内存销毁仍是有必定规律可循,目前GC的垃圾回收策略主要有两种,一种是引用计数,另外一种是不可达检测。目前主流浏览器都实现了上述两种算法,而且都会综合使用两种算法对内存进行优化。可是确实还存在上述算法没法覆盖的点,好比闭包。所以仍是依赖于开发者自己的意识,所以了解下内存泄漏的原理和解决方案仍是很是有用的。下面讲述容易形成内存泄漏的几种状况。
函数调用是有必定的开销的,具体为须要为函数分配栈空间。若是递归调用的话,有可能形成爆栈。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(100) // 一切正常
factorial(1000) // 有可能爆栈,可是如今浏览器作了优化,一般会输出Infinite
}
复制代码
若是在这里你使用了比较复杂的运算状况就会变糟,若是再加上闭包就更糟糕了。
因为js没有私有属性,js若是要实现私有属性的功能,就要借助闭包实现。
function closure() {
var privateKey = 1;
return function() {
return privateKey
}
}
复制代码
可是因为js的垃圾回收机制,js会按期将没有引用的内存释放,若是使用闭包,函数会保持变量的引用,致使垃圾回收周期内不能将其销毁,滥用闭包则可能产生内存泄漏。
上面从前端三层角度分析了性能优化的手段,可是还有一个,并且是占比很是大的资源没有提到,那就是图片。俗话说,一图胜千言,图片在目前的网站中占据了网站中大部分的流量。虽然图片不会阻止用户的交互,不影响关键路径,可是图片加载的速度对于用户体验来讲很是重要。尤为是移动互联网如此发达的今天,为用户节省流量也是很是重要的。所以图片优化主要有两点,一点是在必要的时候使用图片,没必要要的时候换用其余方式。另外一种就是压缩图片的体积。
首先要问问本身,要实现所需的效果,是否确实须要图像。好的设计应该简单,并且始终能够提供最佳性能。若是您能够消除图像资源(与 HTML、CSS、JavaScript 以及网页上的其余资源相比,须要的字节数一般更大),这种优化策略就始终是最佳策略。不过,若是使用得当,图像传达的信息也可能赛过千言万语,所以须要由您来找到平衡点。
首先来看下图片体积的决定因素。这里可能须要一些图像学的相关知识。图片分为位图和矢量图。位图是用比特位来表示像素,而后由像素组成图片。位图有一个概念是位深,是指存储每一个像素所用的位数。那么对于位图计算大小有一个公式就是图片像素数 * 位深 bits。 注意单位是bits,也能够换算成方便查看的kb或者mb。
图片像素数 = 图片水平像素数 * 图片垂直像素数
而矢量图由数学向量组成,文件容量较小,在进行放大、缩小或旋转等操做时图象不会失真,缺点是不易制做色彩变化太多的图象。那么矢量图是电脑通过数据计算获得的,所以占据空间小。一般矢量图和位图也会相互转化,好比矢量图要打印就会点阵化成位图。
下面讲的图片优化指的是位图。知道了图片大小的决定因素,那么减小图片大小的方式就是减小分辨率或者采用位深较低的图片格式。
咱们平时开发的时候,设计师会给咱们1x2x3x的图片,这些图片的像素数是不一样的。2x的像素数是1x的 2x2=4倍,而3x的像素数高达3x3=9倍。图片直接大了9倍。所以前端使用图片的时候最好不要直接使用3倍图,而后在不一样设备上平铺,这种作法会须要依赖浏览器对其进行从新缩放(这还会占用额外的 CPU 资源)并以较低分辨率显示,从而下降性能。 下面的表格数据来自Google Developers
请注意,在上述全部状况下,显示尺寸只比各屏幕分辨率所需资源“小 10 个 CSS 像素”。不过,多余像素数及其相关开销会随图像显示尺寸的增长而迅速上升!所以,尽管您可能没法保证以精确的显示尺寸提供每个资源,但您应该确保多余像素数最少,并确保特别是较大资源以尽量接近其显示尺寸的尺寸提供。
咱们可使用媒体查询或者srcset等针对不一样屏幕加载不一样资源。可是9倍这样的大小咱们仍是很难接受。所以有了下面的方法。
位深是用来表示一个颜色的字节数。位深是24位,表达的是使用256(2的24/3次方)位表示一个颜色。所以位深越深,图片越精细。若是可能的话,减小位深能够减小体积。
前面说了图片大小 = 图片像素数 * 位深
, 其实更严格的是图片大小 = 图片像素数 * 位深 * 图片质量
, 所以图片质量(q)越低,图片会越小。 影响图片压缩质量的因素有不少,好比图片的颜色种类数量,相邻像素颜色相同的个数等等。对应着有不少的图片压缩算法,目前比较流行的图片压缩是webp格式。所以条件容许的话,尽可能使用webp格式。
有了上面的理论以后,咱们须要将理论具体运用在实践上。 咱们平时开发的时候会有缩略图的需求,若是你将原始图片加载过来经过css控制显示的话,你会发现你会加载一个很是大的图片,然而自己应该很小才对。那么若是咱们能够控制只下载咱们须要缩略显示的部分就行了。咱们但愿能够经过https://test.imgix.net/some_file?w=395&h=96&crop=faces
的方式指定图片的大小,从而减小传输字节的浪费。已经有图片服务商提供了这样的功能。好比imgix。
imgix有一个优点就是可以找到图片中有趣的区域并作裁剪。而不是仅仅裁剪出图片的中心
上面提到的webp最好也能够经过CDN厂商支持,即咱们上传图片的时候,CDN厂商对应存储一份webp的。好比咱们上传一个png图片https://img.alicdn.com/test/TB1XFdma5qAXuNjy1XdXXaYcVXa-29-32.png
。而后咱们能够经过https://img.alicdn.com/test/TB1XFdma5qAXuNjy1XdXXaYcVXa-29-32.webp
访问其对应的webp资源。咱们就能够根据浏览器的支持状况加载webp或者png图片了。
第二个有效的方式是懒加载,一个重要的思想就是只加载应该在此时展现的图片。假如你正在使用react,那么你能够经过react-lazyload使用图片懒加载。其余框架能够自行搜索。
import LazyLoad from 'react-lazyload';
<LazyLoad once height={200} offset={50}> <img srcSet={xxx} sizes={xxxxx} /> </LazyLoad> 复制代码
若是你已经采起了很是多的优化手段,用户仍是以为很是慢,怎么办呢?要知道,性能好很差不是数据测量出来的,而是用户的直观感受,就像我开篇讲述的那样。有一个方法能够在速度不变的状况下,让用户感受更快,那就是合理使用动画。如一个写着当前90%进度的进度条,一个奔跑的小熊?可是必定要慎用,由于不合理的动画设计,反而让用户反感,试想一下,当你看到一个期待已久的肯定按钮,可是它被一个奔跑的小熊挡在了身后,根本点不到,你心里会是怎样的?
另外我在这里只是提供了性能优化的思路,并无覆盖性能优化的全部点,好比google的protobuffer能够减小先后端传输数据的体积,进而提高性能。可是咱们 有了上面的优化理论和思想,我相信这些东西都是能够看到并作到的
PS:容许我差一波广告,兑吧前端正在招人~有兴趣的戳我投递简历
欢迎关注个人公众号,不定时更新。