很多人通常在完成了产品之后才会去考虑性能。把与性能相关的事情拖到项目的最后来做,所做的也不过是对服务器上的config文件进行一些微调、串联、优化以及部分特别小的调整。而现在,技术已经有了翻天覆地的变化。一个项目的性能是非常重要的,除了要在技术层面上注意,更要在项目的设计之初就开始考虑,这样才可以使性能的各种隐形需求完美的整合到项目中,随着项目一起推进。
在性能方面,前端的优化是大家很容易忽视却又十分重要的一点。
前端是庞大的,包括 HTML、 CSS、 Javascript、Image 、Flash 等等各种各样的资源。前端优化是复杂的,针对方方面面的资源都有不同的方式。那么,前端优化的目的是什么 ?
▪ 从用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
▪ 从服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。
前端优化的途径有很多,按粒度大致可以分为三类:
第一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等 ;
第二类是服务器端优化,如:添加Expires 或Cache-Control报文头等;
第三类则是代码级别的优化,例如 Javascript 中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。
首先,我把雅虎14条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点做一次梳理,按照优化方向分类,可以得到这样一张表格:
【页面级优化】
一、减少 HTTP请求数
80%的响应时间花在下载网页内容(images, stylesheets, javascripts, scripts, flash等)。减少请求次数是缩短响应时间的关键!可以通过简化页面设计来减少请求次数,但页面内容较多可以采用以下技巧。
1)捆绑文件
现在有很多现成的库可以帮你将多个脚本文件捆绑成一个文件,将多个样式表文件捆绑成一个文件,以此来减少文件的下载次数。
2)CSS Sprites
就是把多个图片拼成一副图片,然后通过CSS来控制在什么地方具体显示这整张图片的什么位置。
咱们来看一个真实的应用案例,如图1:
从图1可以看到这个应用的所有css和js都没有没有合并压缩,我算了一下,总共引入了50个css和js,咱们再开看看图2:
从图2可以看出,一个页面加载最少要4秒,每个css资源http请求就用了两百多毫秒,50个资源就占了一秒多。如果把所有js和css分别合并压缩成一个文件,页面的加载速度就会提高很多。
现在压缩合并js和css的工具很多,但是考虑到现在好多项目都是maven工程,所以推荐大家使用雅虎的yuicompressor工具进行压缩合并,还可以打出API文档(jresui的API文档就是用这个来打的), yuicompressor除了支持maven方式,也支持后台java代码方式进行合并压缩,具体配置如下:
▲将外部脚本置底,将CSS放在head中
浏览器是可以并发请求的,这一特点使得其能够更快的加载资源,然而外链脚本在加载时却会阻塞其他资源。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。解决这一问题的方法有很多,而最简单可依赖的方法就是将脚本尽可能的往后挪,减少对并发下载的影响。
如果将 CSS放在其他地方比如 BODY中,则浏览器有可能还未下载和解析到 CSS就已经开始渲染页面了,这就导致页面由无 CSS状态跳转到 CSS状态,用户体验比较糟糕。除此之外,有些浏览器会在 CSS下载完成后才开始渲染页面,如果 CSS放在靠下的位置则会导致浏览器将渲染时间推迟。
二、静态资源缓存
我们知道,缓存对于前端性能的优化是十分重要的,在正式发布系统的时候,对于那些不经常变动的静态资源比如各种JS、CSS文件、背景图片等等我们会设置一个比较大的缓存过期时间(max-age),当用户再次访问这个页面的时候就可以直接利用缓存而不是重新从服务器获取,这样不仅可以减轻服务端的压力,还可以节约网络传输的流量,同时用户体验也更好(用户打开页面更快了)。
一般的公司对于静态资源以及缓存的处理方式无非就这么几种:
1、 在静态资源后面加一个版本号 v=1.111
2 、 为了准确的确定文件是否修改,将后面的版本号修改为文件摘要(主要根据文件内容生成的一个值)
以上两种处理方式,都会存在一些问题。
第一种方式,需要维护版本号,如果在一个文件中,存在多个资源,那么没有被修改过的资源文件也会被修改版本号,导致不必要的资源加载。
第二种方式,可以精确的发现哪一个文件被修改过。从而要求客户端进行重新加载。但是同样会存在一些问题。
如果先发 html文件:
那么会导致重新加载资源,但一样还是无法访问到最新的特性。(毕竟资源文件还没有真正的更新。),如是Html页面的结构有更新,但加载了旧的资源,很有可能导致页面结构的错乱。并且会缓存资源,直到资源过期,否则除非强制刷新,会一直是错误页面。(这里要注意到,由于第一次加载了旧的资源,版本号又是新的版本号,所以即使在这之后上了资源,这里依旧会读取旧的资源。
如果先发资源文件:
如果之前访问过页面,那就会有保存有本地缓存,那么由于访问的还是缓存文件,不会出现问题。但如果是新用户,那么就会访问到新的资源文件,很有可能导致页面错乱。而等到页面html也发布之后,页面又恢复了正常。
所以还是建议大家通过nginx来配置静态资源缓存过期时间来处理,大概配置如下:
location ~ .*.(js|css)$ {
expires 30d;
}
三、异步执行 (inline)内部脚本
inline脚本对性能的影响与外部脚本相比,是有过之而无不及。首先,与外部脚本一样, inline脚本在执行的时候一样会阻塞并发请求,除此之外,由于浏览器在页面处理方面是单线程的,当 inline脚本在页面渲染之前执行时,页面的渲染工作则会被推迟。简而言之, inline脚本在执行的时候,页面处于空白状态。鉴于以上两点原因,建议将执行时间较长的 inline脚本异步执行,异步的方式有很多种,例如使用 script元素的defer 属性(存在兼容性问题和其他一些问题,例如不能使用 document.write)、使用setTimeout ,此外,在HTML5中引入了 Web Workers的机制,恰恰可以解决此类问题。
四、(Lazy Load)异步加载 Javascript
随着 Javascript框架的流行,越来越多的站点也使用起了框架。不过,一个框架往往包括了很多的功能实现,这些功能并不是每一个页面都需要的,如果下载了不需要的脚本则算得上是一种资源浪费 -既浪费了带宽又浪费了执行花费的时间。目前的做法大概有两种,一种是为那些流量特别大的页面专门定制一个专用的 mini版框架,另一种则是 Lazy Load。YUI 则使用了第二种方式,在 YUI的实现中,最初只加载核心模块,其他模块可以等到需要使用的时候才加载。
五、异步请求 Callback
异步请求 Callback(就是将一些行为样式提取出来,慢慢的加载信息的内容)在某些页面中可能存在这样一种需求,需要使用 script标签来异步的请求数据。类似:
像以上这种方式直接在页面上写
六、减少不必要的 HTTP跳转
对于以目录形式访问的 HTTP链接,很多人都会忽略链接最后是否带 ’/',假如你的服务器对此是区别对待的话,那么你也需要注意,这其中很可能隐藏了 301跳转,增加了多余请求。
避免重复的资源请求
这种情况主要是由于疏忽或页面由多个模块拼接而成,然后每个模块中请求了同样的资源时,会导致资源的重复请求
七、减少cookie传输
一方面,cookie包含在每次请求和响应中,太大的cookie会严重影响数据传输,因此哪些数据需要写入cookie需要慎重考虑,尽量减少cookie中传输的数据量。另一方面,对于某些静态资源的访问,如CSS、script等,发送cookie没有意义,可以考虑静态资源使用独立域名访问,避免请求静态资源时发送cookie,减少cookie传输次数。
八、减少iframe数量
使用iframe要注意理解iframe的优缺点:
▲优点
▪ 可以用来加载速度较慢的内容,例如广告。
▪ 安全沙箱保护。浏览器会对iframe中的内容进行安全控制。
▪ 脚本可以并行下载
▲缺点
▪ 即使iframe内容为空也消耗加载时间
▪ 会阻止页面加载
前端调试
### 一.为什么需要调试
解决bug通常要对代码进行调试,这样才能比较容易的找出关键性问题。掌握一个好的调试技巧是一个开发人员必备的基本技能。
二.基本调试(pc端)
这里我们拿一套iview-admin 的源码来进行调试
1.肉眼调试
所谓肉眼调试就是指直接看代码进行调试,这种方式效率非常低下。
2.console
-
log(...)
查看console.log(...)打印出的日志应该是目前大多数前端惯用的调试方法。但是这种方式对于缕清程序运行的步骤和过程来说是比较困难的。
比如,我要查看一个函数被调用的位置,这就比较难找出。查看下面代码
我们需要找到 updateMenulist() 被调用了几次,在哪被调用的。当然,被调用了几次我们很容易查看,只需要在函数内打印一下即可。
浏览器控制台中就会显示出来
但是我们想知道它是在哪里被调用的,这时我们会想到,直接在文件夹里搜索这个函数不就可以了吗,如
-
搜索到的结果是有4个地方调用了这个函数,但是控制台里只打印了两次,那么,继续往下看。
-
trace(...)
这个console.trace()的概述是:向Web控制台输出一个堆栈跟踪。意思就是打印当前执行位置到console.trace()的路径信息.使用console.trace()可以打印出详细的调用堆栈:
3.断点调试
如果是使用webpack打包了需要将打开map模式,否则断点断不到具体文件。
4.vscode 调试
使用vscode的朋友们可以很方便的在vscode上面进行调试
5.chrome 插件调试
首先,下载vscode 插件
然后点击调试按钮
找到你的项目添加配置
如我的是MyApp,点击添加
然后会跳到一个文件里
将url改成你的项目开发环境调试的地址, 然后 驱动项目 一般的启动命令配置都是 npm run dev,具体的启动命令自行查看package.json文件中。
启动完成之后 按f5启动 chorme调试,这样就可以在 vscode 中打断点进行调试了。
6.vue devTools
vue devTools 使用chrome内核的浏览器可以下载vue devTools拓展来调试vue单页应用,这使得vue项目调试起来非常的方面。
安装地址
如我写的一个后台管理的vue页面。启动项目后。
-
浏览器中断点
之前我们console.log()打印的时候,后面会出现打印的具体文件位置。
-
-
点击之后会跳到这个文件的具体位置。之后点击我箭头所指的位置就断点了
-
-
之后就可以按F5刷新断点调试了,程序运行到这个地方会被卡住,然后你就可以查看当前的环境中的一些信息,如下
-
-
查看调用栈
我们可以很明显的查看函数第一次是在main.js的34行调用的。
然后我们可以按下F11或者:
于是我们再次点击,就又跳到了第二次执行这个函数的地方。
-
代码中的断点
有时候我们不想用console.log()去打印然后在浏览器中找到文件设置断点,那还有一种方法可以快速的实现断点
使用 debugger 关键之
在代码中直接使用 debugger 关键字可以快速的实现断点。
效果和浏览器中的断点效果是一样的。
-
配置launch.json
在项目根目录配置.vscode文件夹。下有个launch.json文件
文件内容
{// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"type": "node","request": "launch","name": "启动程序","program": "${workspaceFolder}/HT/javascript/test.js"}] } 复制代码点击F5或者:
关于vscode 调试的 查阅 go.microsoft.com/fwlink/?lin… 一般是进行服务端后台开发的用vscode调试的比较多。
然后按f12打开开发者工具,找到我们工具栏中的vue选项。于是乎便能清晰的看到我们的vue组件。
二.DOM调试
1.Event Listener Breakpoints(事件监听器断点)
当有事件被触发的时候,事件监听器会断点到具体的事件位置。
例子:
<!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>Document</title>
</head>
<body><div id="cli">测试点击</div>
</body><script>var cli = document.querySelector("#cli");cli.addEventListener("click", function(){console.log("点击了")})</script></html>
复制代码
在浏览器中打开文件,然后按f12找到sources选项卡,比如我要监听点击事件。选择mouse将click打上钩。
然后我们测试点击事件。便可以发现事件被拦截了。
2.Dom breakpoints
subtree modifications : 当子节点发生变化的时候发生断点 attribute modifications : 当属性发生变法的时候触发断点 node removal : 当元素被移除的时候发生断点
XHR断点的强大之处是可以自定义断点规则,这就意味着我们可以针对某一批、某一个,乃至所有异步请求进行断点设置,非常强大。但是,似乎这个功能在日常开发中用得并不多,至少我用得不多。想想原因大概有两点:其一,这类型的断点调试需求在日常业务中本身涉及不多;其二,现阶段的前端开发大多基于JS框架进行,最基本的jQuery也已经对Ajax进行了良好封装,极少有人自己封装Ajax方法,而项目为了减少代码体积,通常选择压缩后的代码库,使得XHR断点跟踪相对不那么容易了。
当我们点击测试点击的时候,会将一个DOM给删除。于是会在删除的代码中实现断点
三.ajax请求调试
1.XHR Breakpoints
注:此小结从别人文章中摘要。
这几年前端开发发生了翻天覆地的变化,从当初的名不见经传到如今的盛极一时,Ajax驱动Web富应用,移动WebApp单页应用风生水起。这一切都离不开XMLHttpRequest对象,而“XHR Breakpoints”正是专为异步而生的断点调试功能。
我们可以通过“XHR Breakpoints”右侧的“+”号为异步断点添加断点条件,当异步请求触发时的URL满足此条件,JS逻辑则会自动产生断点。演示动画中并没有演示到断点位置,这是因为,演示使用的是jQuery封装好的ajax方法,代码已经过压缩,看不到什么效果,而事实上XHR断点的产生位置是”xhr.send()”语句。
XHR断点的强大之处是可以自定义断点规则,这就意味着我们可以针对某一批、某一个,乃至所有异步请求进行断点设置,非常强大。但是,似乎这个功能在日常开发中用得并不多,至少我用得不多。想想原因大概有两点:其一,这类型的断点调试需求在日常业务中本身涉及不多;其二,现阶段的前端开发大多基于JS框架进行,最基本的jQuery也已经对Ajax进行了良好封装,极少有人自己封装Ajax方法,而项目为了减少代码体积,通常选择压缩后的代码库,使得XHR断点跟踪相对不那么容易了。
四.性能调试工具performance
使用Chrome DevTools的performance面板可以记录和分析页面在运行时的所有活动。
在火焰图上看到的圈起来的三条虚线,分别是:
蓝线代表 DOMContentLoaded 事件。 绿线代表首次绘制的时间。