zoukankan      html  css  js  c++  java
  • 前端性能优化

    前端性能优化可以分为两大类,分别是

        页面级别优化:包含了http请求数以及内联脚本位置优化;

        代码级别优化:包含DOM操作优化,CSS选择符优化以及图片优化等。

    优化的目的

    优化的目的在于让页面加载的更快,对用户操作响应更及时,为用户带来更好的用户体验,对于开发者来说优化能够减少页面请求数,能够节省资源。

    页面级别优化

    http请求数

    减少http请求数是最重要也是最有效的方法,可以通过以下方法来减少http请求:

    • 合理的设置http缓存,恰当的缓存设置可以大大减少http请求。要尽可能地让资源能够在缓存中待的更久;
    • 从设计实现层面简化页面,保持页面简洁、减少资源的使用是最直接的
    • 资源合并与压缩,尽可能的将外部的脚本、样式进行合并,多个合为一个
    • CSS Sprites,通过合并CSS图片,这是减少请求数的一个好办法 

    图片地图:

    假设导航栏上有五幅图片,点击每张图片都会进入一个链接,这样五张导航的图片在加载时会产生5个HTTP请求。然而,使用一个图片地图可以提高效率,这样就只需要一个HTTP请求。

     服务器端图片地图:将所有点击提交到同一个url,同时提交用户点击的 x,y坐标,服务器端根据坐标映射响应

    客户端图片地图:直接将点击映射到操作

    使用图片地图的缺点:指定坐标区域时,矩形或圆形比较容易指定,而其他形状手工指定比较难

    CSS Sprites:

    直译过来就是CSS精灵,通过将多个图片融合到一幅图里面,然后通过CSS的一些技术布局到网页上。特别是图片特别多的网站,如果能用css sprites降低图片数量,带来的将是速度的提升。

    字体图标:

    在可以大量使用字体图标的地方我们可以尽可能使用字体图标,字体图标可以减少很多图片的使用,从而减少http请求,字体图标还可以通过CSS来设置颜色、大小等样式。

    合并脚本和样式表

    将多个样式表或脚本合并到一个文件中,可以减少HTTP请求的数量从而缩短响应时间。

    然而合并所有文件对许多人,尤其是编写模块化代码的人来说是不能忍的,而且合并所有的样式文件或者脚本文件可能会导致在一个页面加载时加载了多于自己所需要的样式或者脚本,对于只访问该网站一个(或几个)页面的人来说反而增加了下载量,所以大家应该自己权衡利弊。

    内联脚本的位置

    浏览器是并发请求的,而外链脚本在加载时却常常阻塞其他资源,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。所以说尽可能的将脚本往后挪,减少对并发下载的影响。

    代码级别的优化

    DOM操作优化

    要避免在document上直接进行频繁的DOM操作,可以使用classname代替大量的内联样式修改,对于复杂的UI元素,设置position为absolute或fixed,尽量使用css动画,适当使用canvas,尽量减少css表达式的使用,使用事件代理。

    图片优化

    通过对图片的压缩来起到优化前端性能的作用

    CSS选择符

    大多数人认为,浏览器对CSS的解析是从左往右的,事实上从右往左解析的效率更高,因为第一个id选择基本上就把查找的范围限定了。

    1、使用CDN

    如果应用程序web服务器离用户更近,那么一个HTTP请求的响应时间将缩短。另一方面,如果web服务器离用户更近,则多个HTTP请求的响应时间将缩短。

    CDN(内容发布网络)是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。在优化性能时,向特定用户发布内容的服务器的选择基于对网络拥堵的测量。例如:CDN可能选择网络阶跃数最小的服务器,或者具有最短响应时间的服务器。

    CDN还可以进行数据备份、扩展存储能力,进行缓存,同时有助于缓和Web流量峰值压力。

    CDN缺点:

    • 响应时间可能会受到其他网站流量的影响。CDN服务提供商在其所有客户之间共享Web服务器组。
    • 如果CDN服务质量下降了,那么你的工作质量也将下降。
    • 无法直接控制组件服务器。

    2、将样式表放在头部

    首先说明一下,将样式表放在头部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现,改善用户体验,防止“白屏”。

    我们总是希望页面能够尽快显示内容,为用户提供可视化的回馈,这对网速慢的用户来说是很重要的。

    将样式表放在文档底部会阻止浏览器中的内容逐步出现。为了避免当样式变化重绘页面元素,浏览器会阻塞内容逐步呈现,造成“白屏”。这源自浏览器的行为:如果样式表仍在加载,构建呈现树就是一种浪费。

    3、添加Expires头

    页面的初次访问者会进行很多HTTP请求,但是通过使用一个长久的Expires头,可以使这些组件被缓存,下次访问的时候,就可以减少不必要的HTTP请求,从而提高加载速度。

    Web服务器通过Expires头告诉客户端可以使用一个组件的当前副本,直到指定时间为止,例如:  Expires: Fri, 18 Mar 2016 07:41:53 GMT

    缺点:它要求服务器和客户端时钟严格同步;过期日期需要经常检查。

    HTTP1.1 中引入 Cache-Control 来克服 Expires 头的限制,使用 max-age 指定组件被缓存多久。

    Cache-Control:max-age=12345600

    若同时指定 Cache-Control 和 Expires,则max-age将覆盖Expires头

    4、压缩组件

    从HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持

    Accept-Encoding: gzip,deflate

    如果Web服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来进行压缩。Web服务器通过响应 Content-Encoding 来通知Web客户端。

    Content-Encoding: gzip

    5、使用外部的JavaScript和CSS

    内联脚本或者样式可以减少HTTP请求,按理来说可以提高页面加载的速度。然而在实际情况中,当脚本或者样式是从外部引入的文件,浏览器就有可能缓存它们,从而在以后加载的时候能够直接使用缓存,而HTML文档的大小减少,从而提高加载速度。

    影响因素:

    (1)每个用户产生的页面浏览量越少,内联脚本和样式的论据越强势。譬如一个用户每个月只访问你的网站一两次,那么这种情况下内联样式将会更好。而如果该用户能够产生很多页面浏览量,那么缓存的样式和脚本将会极大减少下载的时间,从而提高加载速度。

    (2)如果你的网站不同页面之间使用的组件大致相同,那么使用外部文件可以提高这些组件的重用率。

    加载后下载

    有时候我们希望内联样式和脚本,但又可以为接下来的页面提供外部文件。那么我们可以在页面加载完成后动态加载外部组件,以便用户接下来的访问。

    function doOnload () {
      setTimeout(downloadFile, 1000)
    }
    
    window.onload = doOnload
    
    function downloadFile () {
      downloadCss('http://abc.com/css/a.css')
      downloadJS('http://abc.com/js/a.js')
    }
    
    function downloadCss (url) {
      var ele = document.createElement('link')
      ele.rel = 'stylesheet'
      ele.type = 'text/css'
      ele.href = url
    
      document.body.appendChild(ele)
    }
    
    function downloadJS (url) {
      var ele = document.createElement('script')
      ele.src = url
      document.body.appendChild(ele)
    }

    在该页面中,JavaScript 和 CSS被加载两次(内联和外部)。要使其正常工作,必须处理双重定义。将这些组件放到一个不可见的 IFrame 中是一个比较好的解决方式。

    6、减少DNS查找

    当我们在浏览器的地址栏输入网址(譬如:www.baidu.com),然后回车,回车这一瞬间看到页面到底发生了什么呢?

    域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(js、css、图片等) --> 浏览器对页面进行渲染呈现给用户。

    域名解析是页面加载的第一步,那么域名是如何解析的呢?以Chrome为例:

    • 1、Chrome浏览器会首先搜索自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存),看自身的缓存中是否有 www.baidu.com 对应的条目,而且没有过期,如果有且没有过期则解析到此结束。 注:可以使用 chrome://net-internals/#dns 来进行查看
    • 2、如果浏览器自身的缓存里面没有找到对应的条目,那么Chrome会搜索操作系统自身的DNS缓存,如果找到且没有过期则停止搜索解析到此结束。注:怎么查看操作系统自身的DNS缓存,以Windows系统为例,可以在命令行下使用 ipconfig /displaydns  来进行查看
    • 3、如果Windows系统的DNS缓存也没有找到,那么尝试读取hosts文件(位于 C:WindowsSystem32driversetc),看看这里面有没有该域名对应的IP地址,如果有则解析成功。
    • 4、如果在hosts文件中也没有找到对应的条目,浏览器就会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器(一般是电信运营商提供的,也可以使用像Google提供的DNS服务器)发起域名解析请求(通过的是UDP协议的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址),运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功,如果没有找到对应的条目,则有运营商的DNS代我们的浏览器发起迭代DNS解析请求,它首先是会找根域的DNS的IP地址(这个DNS服务器都内置13台根域的DNS的IP地址),找到根域的DNS地址,就会向其发起请求(请问 www.baidu.com 这个域名的IP地址是多少啊?)根域发现这是一个顶级域com域的一个域名,于是就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去,于是运营商的DNS就得到了com域的IP地址,又向com域的IP地址发起了请求(请问www.baidu.cin这个域名的IP地址是多少?),com域这台服务器告诉运营商的DNS我不知道 www.baidu.com 这个域名的IP地址,但是我知道 baidu.com 这个域的DNS地址,你去找它去,于是运营商的DNS又向 baidu.com 这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.baidu.com 这个域名的IP地址是多少?),这个时候 baidu.com 域的DNS服务器一查,唉,果真在我这里,于是就把找到的结果发送给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了 www.baidu.com这个域名对应的IP地址,并返回给Windows系统内核,内核又把结果返回给浏览器,终于浏览器拿到了 www.baidu.com 对应的IP地址,该进行下一步的动作了。

      注:一般情况下是不会进行以下步骤地

      如果经过以上的4个步骤,还没有解析成功,那么会进行如下步骤:

    • 5、操作系统就会查找NetBIOS name Cache(NetBIOS名称缓存,就存在客户端电脑中),那这个缓存有什么东西呢?凡是最近一段时间内和我成功通讯的计算机的计算机名和IP地址,就都会存在这个缓存里面。什么情况下该步能解析成功呢?就是该名称正好是几分钟前和我成功通信过,那么这一步就可以成功解析。
    • 6、如果第5步也没有成功,那会查询 WINS 服务器(是NETBIOS名称和IP地址对应的服务器)
    • 7、如果第6步也没有查询成功,那么客户端就要进行广播查找
    • 8、如果第7步也没有成功,那么客户端就读取LMHOSTS文件(和HOSTS文件同一个目录下,写法也一样)

    如果第八步还没有解析成功,那么就宣告这次解析失败,那就无法跟目标计算机进行通信。只要这八步中有一步可以解析成功,那就可以成功和目标计算机进行通信。

    DNS也是开销,通常浏览器查找一个给定域名的IP地址要花费20~120毫秒,在完成域名解析之前,浏览器不能从服务器加载到任何东西。那么如何减少域名解析时间,加快页面加载速度呢?

    当客户端DNS缓存(浏览器和操作系统)缓存为空时,DNS查找的数量与要加载的Web页面中唯一主机名的数量相同,包括页面URL、脚本、样式表、图片、Flash对象等的主机名。减少主机名的数量就可以减少DNS查找的数量。

    减少唯一主机名的数量会潜在减少页面中并行下载的数量(HTTP 1.1 规范建议从每个主机名并行下载两个组件,但实际上可以多个),这样减少主机名和并行下载的方案会产生矛盾,需要大家自己权衡。建议将组件放到至少两个单不多于四个主机名下,减少DNS查找的同时也允许高度并行下载。

    7、精简JavaScript

    精简

    精简就是从代码中移除不必要的字符以减少文件大小,降低加载的时间。代码精简的时候会移除不必要的空白字符(空格、换行、制表符),这样整个文件的大小就变小了。

    混淆

    混淆是应用在源代码上的另外一种方式,它会移除注释和空白符,同时它还会改写代码。在混淆的时候,函数和变量名将会被转换成更短的字符串,这时代码会更加精炼同时难以阅读。通常这样做是为了增加反向工程的难度,这也同时提高了性能。

    缺点:

    • 混淆本身比较复杂,可能会引入错误
    • 需要对不能改变的符号做标记,防止JavaScript符号(譬如关键字、保留字)被修改
    • 混淆会使代码难以阅读,这使得在产品环境中调试问题更加困难

    8、避免重定向

    什么是重定向?

    重定向用于将用户从一个URL重新路由到另一个URL

    常用重定向的类型

    • 301:永久重定向,主要用于当网站的域名发生变更之后,告诉搜索引擎已经变更了,应该把旧域名的数据和连接数转移到新域名下,从而不会让网站的排名因域名变更而受到影响。
    • 302:临时重定向,主要实现post请求后告知浏览器转移到新的URL。
    • 304:Not Modified,主要用于当浏览器在其缓存中保留了组件的一个副本,同时组件已经过期了,这时浏览器就会生成一个条件GET请求,如果服务器的组件并没有修改过,则返回304状态码,同时不携带主体,告知浏览器可以重用这个副本,减少响应大小。

    重定向如何损伤性能?

    当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载。

    重定向的应用场景

    (1)跟踪内部流量

      重定向经常用于跟踪用户流量的方向,当拥有一个门户主页的时候,同时想对用户离开主页后的流量进行跟踪,这时可以使用重定向。例如:某网站主页新闻的链接地址 http://a.com/r/news,点击该链接将产生301响应,其Location被设置为 http://news.a.com。通过分析 a.com 的web服务器日志可以得知人们离开首页之后的去向。

      我们知道重定向是如何损伤性能的,为了实现更好的效率,可以使用Referer日志来跟踪内部流量去向。每个HTTP请求都有一个Referer表示原始请求页(除了从书签打开或直接键入URL等操作),记录下每个请求的Referer,就避免了向用户发送重定向,从而改善了响应时间。

    (2)跟踪出站流量

    有时链接可能将用户带离你的网站,在这种情况下,使用Referer就不太现实了。

    同样也可以使用重定向来解决跟踪出站流量问题。以百度搜索为例,百度通过将每个链接包装到一个302重定向来解决跟踪问题,例如你搜索关键字“前端性能优化”,搜索结果中的一个URL为

    https://www.baidu.com/link?url=pDjwTfa0IAf_FRBNlw1qLDtQ27YBujWp9jPN4q0QSJdNtGtDBK3ja3jyyN2CgxR5aTAywG4SI6V1NypkSyLISWjiFuFQDinhpVn4QE-uLGG&wd=&eqid=9c02bd21001c69170000000556ece297,  即使搜索结果并没有改变,但这个字符串是动态改变的。

    出了重定向外,我们还可以选择使用信标(beacon)--- 一个HTTP请求,其URL中包含有跟踪信息。跟踪信息可以从信标Web服务器的访问日记中提取出来,信标通常是一个 1px * 1px 的透明图片,不过204响应更优秀,因为它更小,从来不被缓存,而且绝不会改变浏览器的状态。

    9、使ajax可缓存

    什么样的ajax请求可以被缓存?

    POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码 200。(可以在服务器端对数据进行缓存,以便提高处理速度)

    GET请求,是可以(而且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一个地址的ajax请求,不会重复在服务器执行,而是返回304.

    ajax请求使用缓存

    在进行ajax请求的时候,可以选择尽量使用get方法,这样可以使用客户端进行缓存,提高请求速度。

  • 相关阅读:
    centos7下redis6的安装
    Centos MySQL 5.7安装、升级教程
    Python之SSH-paramiko模块的使用
    Linux进程管理工具之Supervisor的使用
    Python-RPC框架之-ZeroRPC和SimpleXMLRPCServer
    Django入门--简单的form验证
    redis常用指令
    MySQL自增主键ID重新排序
    批量微博删除脚本
    解决springboot2.x集成redis节点故障redisTemplate报错redis Command timed out
  • 原文地址:https://www.cnblogs.com/haishen/p/11947290.html
Copyright © 2011-2022 走看看