Effective前端7:加快页面打开速度
ramonzheng
8年前
<p>页面的打开速度对网站的优化有极大的意义,如果打开一个页面一直处于白屏状态,若超过5s,可能大部份人都会把它关了。或者是页面加载出来了,但是比较慢,页面显示不完整,标签栏一直在转圈,页面长期处于不可交互的状态,这也是一种很不好的体验。</p> <p>如何评价一个页面打开得快不快,可以用两个指标描述,一个是ready的时间,另一个是load的时间。这个可以从Chrome的控制台看到,如打开stackoverflow的首页:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9938e9e380e8fd77d3b2c94f38457003.png"></p> <p>一共是加载490KB,ready时间是7.36s,load时间是17.35s。再来看下打开谷歌的情况:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/65f8de24cb63154d4cd1b22ab7eb2f2c.png"></p> <p>虽然两个页面的内容差别比较大,但是从时间来看的话,很明显谷歌的速度要明显优于stackoverflow,谷歌的ready时间只有2.22s,也就是说2.22秒之后带个页面就是布局完整可交互的了,而stackoverflow打开的时候较长时间处于空白状态,可交互时间达要到7.36s。</p> <p>从load时间来看的话,两者差别不太,都比较长,可能因为它们是境外的服务器。finish时间比load时间长,是因为load完后又去动态加载了其它的js。</p> <p>为什么stackoverflow的ready时间要这么长呢?下面分点介绍优化的策略</p> <h2>1. 减少渲染堵塞</h2> <h3>(1)避免head标签JS堵塞</h3> <p>所有放在head标签里的css和js都会堵塞渲染。如果这些CSS和JS需要加载和解析很久的话,那么页面就空白了。看stackoverflow的html结构:</p> <pre> <code class="language-css"><head> <title>Stack Overflow</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> </head> </code></pre> <p>它把jquery放到了head标签里,这个jquery加载了3s:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ffc6db3d54caf00dc5b8fa30c68f750b.png"></p> <p>相比之下,html文件只加载了0.83s,所以这个js文件至少使页面停留了3s的空白状态。</p> <p>它的解析花了20ms不到:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ecca740f4ecd6feeb3a0e9b6826e664d.png"></p> <p>这个解析时间还是可以忽略,相对于加载时间而言。并且我们注意到即使把jquery给删了,stackoverflow的页面还是可以完整显示的,样式无异,这个可以复制它的源代码到本地然后删掉head里面的script标签打开页面观察。也就是话,把js放头部是没太大必要的。最关键的是它用的是谷歌的cdn,这样就导致了大陆的小伙伴们无法在正常的环境下看stackoverflow,一打开整个页面一两分钟都保持空白态。</p> <p>有两种解决办法,第一种是把script放到body后面,这也是很多网站采取的方法。第二种是给script加defer的属性,defer是html5新增的属性。一旦script是defer延迟的,那么这个script将会异步加载,但不会马上执行,会在 readystatechange变为 Interactive后按顺序依次执行,用以下demo做说明:</p> <pre> <code class="language-css"><!DOCType html> <html> <head> <metacharset="utf-8"> <script src="defer.js" defer></script> </head> <body> <script> document.onreadystatechange = function(){ console.log(document.readyState); }; </script> <script> window.onload = function(){ console.log("window is loaded"); }; window.addEventListener("DOMContentLoaded", function(){ console.log("dom is ready"); }); </script> <imgsrc="test.jpg" alt=""> <script src="normal.js"></script> <script> console.log("dom almost built"); </script> </body> </html> </code></pre> <p>defer.js的内容为:</p> <pre> <code class="language-css">console.log("I'm defered"); for(var i = 0; i < 10000000; i++){ new Date(); } console.log("defer script end"); </code></pre> <p>中间让它执行一段较长的时间(约3秒),normal.js内容类似,打印的log:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0c365a79b45dd2bb0a49ac95e795a709.png"></p> <p>可以看到,正常的script最先执行,然后紧接着的内联script依次执行,说明正常script是串行执行的,不过它们可以是并行加载,例如stackoverflow写在head标签里面的这两个正常的script是并行加载的:</p> <p>回到上面,第四行readystatechange变成interactive,然后开始执行defer的script,执行完后依次触发ready和load事件。</p> <p>也就是说,defer的脚本会正常加载,但是延后执行,在interactive后执行,所以不会block页面渲染。因此放在head标签的script可以加一个defer,但是加上defer的脚本发生了重大的变化,不能够影响渲染DOM的过程,只能是在渲染完了,例如绑的click事件在整个页面没渲染好之前不能生效。并且很多人要把它写在head里面,是为了在页面中间的script能调用到这些库,影响DOM的渲染,加上defer就违背本意了。但是把script写在head标签始终是不推荐的,所以在页面中间的script要么使用原生的API,要么把一些用到的函数写成head标签里面的内联script。</p> <p>head标签里面的defer脚本跟放在body后面的脚本有什么区别呢,区别在于defer的脚本会马上加载,一旦页面interactive了便可以马上执行,而放body后面是document快interactive的时候才去加载。我们知道,浏览器同时加载资源数是有限的,例如Chrome对同一个域名的资源,每次最多同时只能建立6个TCP连接(读者可以试试)。所以写在head标签里面的外链脚本会影响页面图片的加载,特别是当脚本很多时。因此,如果页面的交互比图片的展示更重要,那么把script写在head标签加上defer是可取的,如果页面的展示更为重要,那么应该把脚本放在body后面。</p> <p>另外,defer可能会有兼容性问题,在老的浏览器上某些行为表现可能会不一致。</p> <p>同样地,head标签里面的CSS资源也会block页面渲染。</p> <h3>(2)减少head标签里的CSS资源</h3> <p>由于CSS必须要放在head标签里面,如果放在body里面,一旦加载好之后,又会对layout好的dom进行重排,样式可能又会发生闪烁。但是一旦放在head标签里面又会堵塞页面渲染,若要加载很久,页面就会保持空白状态。所以要尽可能地减少CSS的代码量。</p> <p>a) 不要放太多base64放在CSS里面</p> <p>放太多base64放在CSS里面,会导致CSS极速膨胀,把一张3k的图片转成base64,体积将会变成4k:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/43c1d94a3ef12d513134c97b185a479c.png"></p> <p>假设放了10张3k的图片,那么CSS将增大40K,这已经和一个普通大小的CSS文件相仿。笔者曾为了解决一个hover变色的问题:</p> <p><img src="https://simg.open-open.com/show/51e681c74264be03a31addacc8296e1d.png"></p> <p>原始图片都是svg的格式,hover的时候变成蓝色,如果这样写的话:</p> <pre> <code class="language-css">.img{ background: url(black.svg) 0 0 no-repeat; } .img:hover{ background: url(blue.svg) 0 0 no-repeat; } </code></pre> <p>会导致hover的时候才去加载blue.svg,第一次hover的时候不会马上变蓝,要稍微等到图片下载下来了,在产品角度上是不可接受的。所以第一种解决办法是把hover写在svg里面,如下:</p> <pre> <code class="language-css"><svg> <style type="text/css"> .st0{fill:#282828;} .st0:hover{fill: #3399cc;} </style> </svg> </code></pre> <p>但是由于下面的文字也要跟着变蓝,文字又不能放在svg里面控制,svg变成外链引进来之后,它就跨域了,无法在外面的html用CSS控制一个跨域的svg的样式,如果把svg变成内联的,又会导致html体积过大,同时对缓存也是不利的。所以当时提议,把svg转成base64放到CSS里面,黑色和蓝色的各转成base64,总共要转6 * 2 = 12个,由于原始的svg本来就比较大,再转成base64就更大了,7k变成了9k,再乘以12,整个CSS就增加了100多K。这样就导致了CSS要加载很久。</p> <p>最后压缩后的CSS文件有179Kb:</p> <p>-rw-r–r– 1 yincheng staff <strong>179K</strong> Mar 10 17:45 common-fbc013bb2526235952078ccd72a7fc97.css</p> <p>好在开启了gzip压缩,实际传输的大小为30K:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3163e2c98f89b56a792dc38645f957d1.png"></p> <p>不管怎样,这种方法依旧是不推荐的。最后采取了图标字体的解决方案,将svg转成字体icon,方便颜色控制。脱离图片后的CSS文件只有22Kb:</p> <p>-rw-r–r– 1 yincheng staff <strong>22K</strong> Mar 10 17:45 common-def3ac6078614e995ca8.js</p> <p>gzip压缩后不到10Kb。</p> <p>上面的例子是避免动态加载,而有时候要动态加载,当使用媒体查询的时候,如下所示:</p> <pre> <code class="language-css">@media(min-width: 501px){ .img{ background-picture: url(large.png); } } @media(min-width: 501px){ .img{ background-picture: url(small.png); } } </code></pre> <p>上面大屏的时候加载large的图片,小屏的时候加载small的图片,浏览器会根据屏幕大小自动加载相应的图片。而一旦你把它转成了base64之后,它们都在CSS里面了,就没有自动加载的优势了。</p> <p>b)把CSS写成内联的:</p> <p>如果你的CSS只有10K或者20K,把它写成内联的,也未尝不可,谷歌和淘宝PC就是这样干的,直接把页面几乎所有的CSS都写成style标签放到html里面:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a3a2315d45a39b43b5e4f6c58d071024.png"></p> <p>这个原始的CSS有66Kb:</p> <p>-rw-r–r– 1 yincheng staff 66K Mar 12 10:00 taobao.css</p> <p>gzip之后为16Kb:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7cc091fc747f28bd6b39e79835dfdbff.png"></p> <p>这样虽然对缓存不利,但是对于首次加载是有很大的作用的。因为如果你把CSS放到CDN上,为了得到这个CSS,它首先需要进行域名解析,然后建立http/https连接,其次才是下载。而用来做域名解析和建立连接的时候很可能早已把放在html里面的CSS下载下来了,这个时间可以从Chrome的控制台观察到:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/019b96c5d5a4aad1c8e806f328923177.png"></p> <p>为了加载这个资源,DNS查找花掉了0.5s,建立tcp连接花掉了0.95s,建立https连接花掉了0.6s,从发请求到收到第一个字节的数据(Time To First Byte)又花掉了1.27s,总的时间接近3s。</p> <p>所以这个开销还是很大的。还不如直接把CSS嵌到HTML里面。如果你的CSS体积没达到3位数,加上gzip压缩,放到html里面应该是可取的。</p> <h2>2. 优化图片</h2> <h3>(1)使用响应式图片</h3> <p>响应式图片的优点是浏览器能够根据屏幕大小、设备像素比ppi、横竖屏自动加载合适的图片,如下使用srcset:</p> <pre> <code class="language-css"><imgsrcset="photo_w350.jpg 1x, photo_w640.jpg 2x" src="photo_w350.jpg" alt=""> </code></pre> <p>如果屏幕的ppi = 1的话则加载1倍图,而ppi = 2则加载2倍图,手机和mac基本上ppi都达到了2以上,这样子对于普通屏幕来说不会浪费流量,而对于视网膜屏来说又有高清的体验。</p> <p>如果浏览器不支持srcset,则默认加载src里面的图片。</p> <p>但是你会发现实际情况并不是如此,在Mac上的Chrome它会同时加载srcset里面的那张2x的,还会再去加载src里面的那张,加载两张图片。顺序是先把所有srcset里面的加载完了,再去加载src的。这个策略比较奇怪,它居然会加载两张图片,如果不写src,则不会加载两张,但是兼容性就没那么好。这个可能是因为浏览器认为,既然有srcset就不用写src了,如果写了src,用户可能是有用的。</p> <p>而使用picture就不会加载两张:</p> <pre> <code class="language-css"><picture> <sourcesrcset="banner_w1000.jpg" media="(min-width: 801px)"> <sourcesrcset="banner_w800.jpg" media="(max-width: 800px)"> <imgsrc="banner_w800.jpg" alt=""> </picture> </code></pre> <p>如上,如果页面宽度大于800px(PC),则加载大图,而在手机上加载小图。这样写浏览器就只会加载source里面的一张图片。但是如果是用js动态插进去的,它还是会去加载两张,只有写在html里面加载初始化页面的时候才只加载一张。</p> <p>这个的解决方法很简单,浏览器支不支持srcset,可以用js判断。如果支持,则不写src的属性了,如果不支持就不用写srcset了:</p> <pre> <code class="language-css">var supportSrcset = 'srcset' in document.createElement('img'); var surportPicture = 'HTMLPictureElement' in window; </code></pre> <p>picture必须要写img标签,否则无法显示,对picture的操作最后都是在img上面,例如onload事件是在img标签触发的,picture和source是不会进行layout的,它们的宽和高都是0。</p> <p>另外使用source,还可以对图片格式做一些兼容处理:</p> <pre> <code class="language-css"><picture> <sourcetype="image/webp" srcset="banner.webp"> <imgsrc="banner.jpg" alt=""> </picture> </code></pre> <p>上面Chrome浏览器将会加载webp格式的图片:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2ef4b125415fd832ec00f8b610b11d8e.png"></p> <p>webp在保持同等清晰度的情况下,体积可以减少一半,但是目前只有Chrome支持,Safari和firefox一直处于实验阶段,所以其它的浏览器如firefox将会加载jpg格式的照片:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8324b57d22f4634d87d443151b512dcc.png"></p> <p>可以看到原图是68k,转成webp之后变成了45k,如果你把jpg有损压得比较厉害,例如质量压为0.3,可以比webp更小,但是失真也比较厉害了。</p> <h3>(2)延迟加载图片</h3> <p>对于很多网站来说,图片往往是占据最多流量和带宽的的资源。特别是那种瀑布式展示性的网站,一个页面展示50本书,50张图片,如果一口气全部放出来,那么页面的Loaded时间将会较长,并且由于并行加载资源数是有限,图片太多会导致放body后面的js解析比较慢,页面将较长时间处于不可交互状态。所以不能一下子把全部图片都放出来,这对于手机上的流量也是不利的。</p> <p>为此,笔者做了个懒惰加载图片的尝试,初始加载页面的时候并不去加载图片,只有当用户下滑到相应位置的时候才把图片放出来。首先,渲染页面的时候别把图片地址放到src上,放到一个data的属性:</p> <pre> <code class="language-css"><picture> <sourcedata-srcset="photo_w350.jpg 1x, photo_w640.jpg 2x"> <imgdata-src="photo_w350.jpg" src="about:blank" alt=""> </picture> </code></pre> <p>如上,放到data-src和data-srcset里面,上面把src的属性写成了”about:blank”,这是因为不能随便写一个不存在的地址,否则控制台会报错:加载失败,如果写成空或不写,那么它会认为src就是当前页面。如果写成about:blank,大家相安无事,并且不同浏览器兼容性好。</p> <p>接下来进行位置判断,监听scroll事件,回调函数为:</p> <pre> <code class="language-css">showImage(leftSpace = 500){ var scrollTop = $window.scrollTop(); var $containers = this.$imgContainers, scrollPosition = $window.scrollTop() + $window.height(); for(var i = 0; i < $containers.length; i++){ //如果快要滑到图片的位置了 var $container = $containers.eq(i); if($container.offset().top - scrollPosition < leftSpace){ this.ensureImgSrc($container); } } } </code></pre> <p>第5行for循环,依次对所有的图片做处理,第8行的if判断,如果滑动的位置快要到那张图片了,则把src放出来,这个位置差默认为500px,如果图片加载得快的话,这种行为对于用户来说是透明的,他可能不知道图片是往下滑的时候才放出来的,几乎不会影响体验,如果用户滑得很快,本身不做这样的处理,也不可能加载得这么快,也是loading的状态。</p> <p>下面的函数把图片放出来:</p> <pre> <code class="language-css">ensureImgSrc($container){ var $source = $container.find("source"); if($source.length && !$source.attr("srcset")){ $source.attr("srcset", $source.data("srcset")); } var $img = $container.find("img:not(.loading)"); if($img.length && $img.attr("src").indexOf("//") < 0){ $img.attr("src", $img.data("src")); this.shownCount++; } } </code></pre> <p>代码里面判断src是不是有”//”,即为正常的地址,如果没有给它赋值,触发浏览器加载图片。并记录已经放出来的个数,这样可以做个优化,当图片全部都加载或者开始加载了,把scroll事件取消掉:</p> <pre> <code class="language-css">init(){ //初始化 var leftSpace = 0; this.showImage(leftSpace); //滑动 $window.on("scroll", this, this.throttleShow); } ensureImgSrc($container){ //如果全部显示,off掉window.scroll if(this.shownCount >= this.allCount){ $window.off("scroll", this.throttleShow); } } </code></pre> <p>这样可以大大减少打开页面的流量,加快ready和load的时间。</p> <h2>3. 压缩和缓存</h2> <h3>(1)gzip压缩</h3> <p>上文已提及,使用gzip压缩可以大大减少文件的体积,一个180k的CSS文件被压成了30k,减少了83%的体积。如何开启压缩呢,这个很简单,只要在nginx的配置里面添加这个选项就好了:</p> <pre> <code class="language-css">server{ gzipon; gzip_types text/plainapplication/javascriptapplication/x-javascripttext/javascripttext/xmltext/css; } </code></pre> <h3>(2)Cache-Control</h3> <p>如果没有任何缓存策略,那么对于以下页面:</p> <pre> <code class="language-css"><!DOCType html> <html> <head> <linkhref="test.css" rel="stylesheet"> </head> <body> <picture> <sourcetype="image/webp" srcset="banner.webp"> <imgsrc="banner.jpg" alt=""> </picture> <script src="normal.js"></script> </body> </html> </code></pre> <p>总共有4个资源,html、css、img和js各一个,第一次加载都为200:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/336acec0b2b9bcda47e956d0dd5f980b.png"></p> <p>刷新页面第二次加载时:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/4ee83cc9a5b891f314a73b0bbb0991d1.png"></p> <p>除了html,其它三个文件都是直接在本地缓存取的,这个是Chrome的默认策略。html是重新去请求,nginx返回了304 Not Modified。</p> <p>为什么nginx知道没有修改呢,因为在第一次请求的时候,nginx的http响应头里面返回了html的最近修改时间:</p> <p><img src="https://simg.open-open.com/show/7ec9a6f9a10bcfc265883ffbded2bd44.png"></p> <p>在第二次请求的时候,浏览器会把这个Last-Modified带上,变成If-Modified-Since字段:</p> <p><img src="https://simg.open-open.com/show/c2c544444e77472c4b15dc03d61a5d1d.png"></p> <p>这样nginx就可以取本地文件信息里的修改时间和这个进行比较,一旦时间一致或者在此之前,直返回304,告诉客户端从缓存取。笔者的nginx版本默认是开启last-modified,有些网站并没有开启这个,每次都是200重新请求。如果把文件编辑了保存,nginx会重新返回一个最近修改时间。</p> <p>除了last-modified字段之外,还可以手动控制缓存时间,那就是使用Cache-Control,例如设置图片缓存30天,而js/css缓存7天:</p> <pre> <code class="language-css">location ~* \.(jpg|jpeg|png|gif|webp)$ { expires 30d; } location ~* \.(css|js)$ { expires 7d; } </code></pre> <p>这样响应头就会加一个 Cache-Control: max-age=604800(s) :</p> <p><img src="https://simg.open-open.com/show/3163e2c98f89b56a792dc38645f957d1.png"></p> <p>这个和last-modified有什么区别呢,如果把expires改成3s:</p> <pre> <code class="language-css">location ~* \.(css|js)$ { expires 3s; } </code></pre> <p>不断刷新,观察加载情况:</p> <p>第一次请求还是200,第二次请求css/js都是cached,过了3秒之后的第三次请求,css/js变成了304:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0a8fa58e8b48b5902b109e2b627c0038.png"></p> <p>从这里可以看出max-age的优先级要大于last-modified。如果要强制不缓存,则把expires时间改成0.</p> <p>上面的结果都是用Chrome实验,firefox的和Chrome比较一致,而Safari差别比较大,即使设置Cache-Control,仍然还是会有304的请求,并且html永远是200重新加载,它没有把last-modified和cache-control带上。</p> <p>综上,设置缓存的作用一个是把200变成304,避免资源重新传输,第二个是让浏览器直接从缓存取,连http请求都不用了,这样对于第二次访问页面是极为有利的。设置缓存还有第三种技术,使用etag。</p> <h3>(3)使用etag</h3> <p>上面的两种办法都有缺点,由于很多网站使用模板渲染,每次请求都是重新渲染,生成的文件的last-modified肯定是不一样的,所以last-modified在这种场景下失效,而使用max-age你无法知道精确控制页面的数据什么时候会发生变化,所以max-age不太好使。这个时候etag就派上用场了。nginx开启etag只需要在server配置里面加上一行:</p> <pre> <code class="language-css">etagon; </code></pre> <p>所谓etag就是对文件做的一个校验和,第一次访问的时候,响应头里面返回这个文件的etag,浏览器第二次访问的时候把etag带上,nginx根据这个etag和新渲染的文件计算出的etag进行比较,如果相等则返回304。</p> <p>如下,第一次访问返回etag:</p> <p><img src="https://simg.open-open.com/show/d7f3a04947aa1633f1e12748b5eea4a8.png"></p> <p>第二次访问带上etag,在If-None-Match字段:</p> <p><img src="https://simg.open-open.com/show/2a105f81a34be425cd66c526a17756c7.png"></p> <p>服务返回304,如果我把html文件修改了,那么这个etag就会发生变化,服务返回200:</p> <p><img src="https://simg.open-open.com/show/67bae867e4a9ace27df68b1c601b925a.png"></p> <p>由于etag要使用少数的字符表示一个不定大小的文件,所以etag是有重合的风险,如果网站的信息特别重要,连很小的概率如百万分之一都不允许,那么就不要使用etag了。</p> <p>我们可以看到youku就是用的etag:</p> <p><img src="https://simg.open-open.com/show/792e45b5601aba17d841664b79a807ac.png"></p> <p>使用etag的代价是增加了服务器的计算负担,特别是当文件比较大时。</p> <h2>4. 其它优化方案</h2> <h3>(1)DNS预读取</h3> <p>上文已提到域名解析可能会花很长的时间,而一个网站可能会加载很多个域的东西,例如使用了三个自已子域名的服务,再使用了两个第三方的CDN,再使用了百度统计/谷歌统计的代码,还使用了其它网站上的图片,一个网站很可能要加载七、八个域的资源,第一次打开时,要做七、八次的DNS查找,这个时间是非常可观的。因此,DNS预读取技术能够加快打开速度,方法是在head标签里面写上几个link标签:</p> <pre> <code class="language-css"><linkrel="dns-prefection" href="https://www.google.com"> <linkrel="dns-prefection" href="https://www.google-analytics.com"> <linkrel="dns-prefection" href="https://connect.非死book.net"> <linkrel="dns-prefection" href="https://googleads.g.doubleclick.net"> <linkrel="dns-prefection" href="https://staticxx.非死book.com"> <linkrel="dns-prefection" href="https://stats.g.doubleclick.net"> </code></pre> <p>如上,对以上向个网站提前解析DNS,由于它是并行的,不会堵塞页面渲染。这样可以缩短资源加载的时间。</p> <h3>(2) html优化</h3> <p>把本地的html布署到服务器上前,可以先对html做一个优化,例如把注释remove掉,把行前缩进删掉,如下处理前的文件:</p> <pre> <code class="language-css"><!DOCType html> <html> <head> <meatacharset="utf-8"> </head> </html> <body> <!-main content--> <div>hello, world</div> </body> </html> </code></pre> <p>处理后的文件:</p> <pre> <code class="language-css"><!DOCTypehtml> <html> <head> <meatacharset="utf-8"> </head> </html> <body> <div>hello, world</div> </body> </html> </code></pre> <p>这样处理的文件可以明显减少html的体积,特别是当一个tab是4个空格或者8个空格时。</p> <p>可以作一个比较,以youku为例,把它的html复制出来,然后再把它每行的行首空格去掉:</p> <p><img src="https://simg.open-open.com/show/bf4d2e737356b9d29048eeb5d82eb8fc.png"></p> <p>从687K减少了200Kb,约为1/3,这个量还是很可观的。对其它网页的实验,可以发现这样处理普遍减少1/3的体积。而且这样做几乎是没有风险,除了pre标签不能够去掉行首缩进之外,其它的都正常。</p> <h3>(3)代码优化</h3> <p>对自己写的代码做优化,提高运行速度,例如说html别嵌套太多层,否则加重页面layout的压力,CSS的选择器别写太复杂,不然匹配的计算量会比较大,对JS,别滥用闭包,闭包会加深作用域链,加长变量查找的时间,如下:</p> <pre> <code class="language-css">var a = 15; function foo(){ var b = a + 3; function bar(){ var c = b + a; } return bar(); } </code></pre> <p>在bar这个函数里面,它为了查找a这个变量,需要在bar的作用域,再到foo的作用域,再到全局作用域进行查找。</p> <p>上文从页面堵塞、图片优化、开启缓存、代码优化等角度介绍了优化页面加载的方案,但其实上面的只是一些参考建议,可能不能放之四海皆通,读者应该要结合自己网站的实际情况做一些分析,找到瓶颈问题。如果不确实就反复实践,直到发现一些合适的方法。</p> <p> </p> <p>来自:http://www.renfed.com/2017/03/12/page-speed/</p> <p> </p>