web前端页面性能优化笔记

包括:资源的合并与压缩,图片编码原理和类型选择,浏览器的渲染机制,懒加载预加载,浏览器存储,缓存机制,PWA,Vue-SSR等页面优化的功能。

1.基础优化:

     1.1 资源的合并与压缩:

            1.1.1 理解减少http请求减少请求资源大小的优化:包括html压缩,css压缩,js压缩,文件合并,开启gzip。

                  ① html压缩:包括空格,制表符,换行符等,以及HTML注释都可以被压缩。一般一个24KB的html文件压缩之后,是22-23KB大小,看是html压缩不大,但是以谷歌来说,如果1KB(即1024b字节)经过HTML压缩减少一个字节,那么每年也能替谷歌节约500TB流量,约近亿元的节约量。

                        如何进行html压缩?:包括在线网站压缩(如http://tool.oschina.net),nodejs提供了html-minfier工具,后端模块引擎渲染压缩。目前很多框架的构建工具,如vue-cli,webpack等都已经在打包项目代码的时候进行了html压缩。

                  ② css压缩:包括删除无效css代码,如注释,合并相同css代码等。

                        如何进行css压缩?:包括在线网站压缩(如http://tool.oschina.net),nodejs提供了html-minfier工具,使用clean-css对css进行压缩。目前很多框架的构建工具,如vue-cli,webpack等都已经在打包项目代码的时候进行了css压缩。

                  ③ js压缩:包括删除无效代码/字符,剔除注释,代码语义的缩减和优化,代码保护(让人不容易一眼看清晰代码)。

                        如何进行js压缩?:包括在线网站压缩(如http://tool.oschina.net),nodejs提供了html-minfier工具,使用uglifyjs2对js进行压缩。目前很多框架的构建工具,如vue-cli,webpack等都已经在打包项目代码的时候进行了js压缩。

                  ④ 文件合并:即将所要的请求,合并到一个文件,一起发送给客户端。不合并文件时,请求一次,返回一次,存在多次请求,多次返回,就会有网络延迟,网络故障.....等问题。合并文件时,将所有请求合并到一个文件,然后在一次性发送给后端,减少了网络请求的的次数以及频繁请求带来的问题,但是也会存在首屏渲染问题(因为是合并到一个文件里所有该文件较大,在第一次请求时,会存在较慢,如果浏览器需要根据请求返回的数据才进行渲染,即存在首屏加载渲染页面比较慢),以及缓存失效的问题(即多个文件合并到一个文件,如果其中一个文件发送细微的改变,那么合并后的文件都需要重新获取请求,即原合并后的缓存失效,解决:公共库的合并,不同页面的合并(单页面的合并))。

                        如何进行文件合并?:包括在线网站文件合并(如http://tool.oschina.net),nodejs实现合并。目前很多框架的构建工具,如vue-cli,webpack等都已经在打包项目代码的时候进行了文件合并。

            1.1.2 掌握压缩与合并的原理:

            1.1.3 通过在线网站和fis3两种方式实现优化:

                  ① fis3:是百度的构建化的工具,是基于前端和后端一起的构建化工具(相比wepack更完整)。

     1.2 浏览器的一个请求从发送到返回经历了什么?

文字解析:用户在浏览器中输入一个url,浏览器将url拆分解析,然后将解析后的domain发送到dns服务器上,dns服务器根据domain查询相关的ip地址,然后将ip地址返回给浏览器,浏览器然后将ip以及相关参数通过网络(局域网,路由器,主干网)发送到服务端,服务端有MVC架构,服务端通过model,redis+db返回view数据视图,最后返回render,在浏览器页面进行渲染html,css,html.....。

请求过程中潜在的性能优化点:dns是否可以通过缓存减少dns查询时间?网络请求过程中走最近的网络环境?相同的静态资源是否可以缓存?能否减少http请求大小?减少http请求次数?改为服务端渲染?

     1.3 图片优化:

          1.3.1:主要包括如下图片

               ① 有损压缩:JPG格式的图片压缩,但是损失的部分,在肉眼上视觉上没有多大影响,压缩率较高,不支持透明。

               ② 无损压缩:PNG格式的图片压缩,支持透明,浏览器兼容好,存在降阶。

               ③ webp压缩:webp压缩程度更好,但在ios webview上有兼容问题,在安卓上很好的支持。

               ④ svg矢量图:通过HTML标签代码内嵌,相对较小,适用于图片样式相对简单的场景,如绘制logo。

               ⑤ 使用iconfont图标替代图片:通过使用iconfont替代图片,iconfont是用svg绘制。

          1.3.2:在线压缩图片网站:https://tinypng.com/

          1.3.3:在线图片格式转换:即png、webp相互转换,如https://zhitu.isux.us/。也可通过fis3插件配置在代码中,实现png、jpg、webp图片的格式的相互转换。

          1.3.4:inline-image:即图片内嵌,将图片转换为 Data url base64格式,参考https://blog.csdn.net/u014465934/article/details/83750121。fis3中存在封装的__inline('img/1.webp')方法,可以直接将'img/1.webp'图片转换为图片内嵌。图片内嵌的特点:

            ① Data URI  替换 img src :减少外部资源Http请求,

            ② 使用 css background-image: url("data:image/gif;base64,R0lGODlhAwAD): 实现缓存

            ③ Data URI 转换后的图片的体积偏大 不适用于大图:适用于图片小于4M的图片。

#在fis3中
var inlineImage=__inline('img/1.webp');
<img src="'+inlineImage+'">

           1.3.5:雪碧图:雪碧图css位置生成网站www.spritecow.com。将多张图片通过photoshop合并到一张图片上,可以减少http请求,但是会增加该图片的大小。


拓展:

     png8/png24/png/32之间的区别:图片的大小,以及图片色彩的丰富度。

          png8——256色+支持透明,大小较小。

          png24——2^24色+不支持透明

          png32——2^24色+支持透明


2.进阶优化:一个网站在浏览器端是如何进行渲染的?

     2.1 html渲染的特点:

          2.1.1:顺序执行,并发加载:html代码解析的顺序是从上到下执行的,即html是顺序执行的,html资源是并发请求的,在某个域名下,并发请求数是有上限的。

          2.1.2:是否阻塞:css的加载是否阻塞后续Js的加载和执行,以及页面的渲染。js的加载是否阻塞后续Js的加载和执行.....,推荐css样式在html的head标签中通过link引入,在css加载完之前,后面的js的执行是会被阻塞的,css不阻塞外部脚本的加载(因为外部加载可以是异步加载,预加载)但是可能会阻塞执行。直接通过srcript引入js会阻塞页面的渲染(因为js可能会动态修改dom结构),js不阻塞资源的加载,js顺序执行,阻塞后续的js逻辑的执行。

          2.1.3:依赖关系:HTML在渲染过程中依赖关系。如页面已经渲染出来,但是css还没加载完成,或css加载很慢,之后突然闪烁一下页面,然后css才加载完成并渲染到页面了,这是没有遵循好依赖关系。有比如js的执行是否存在依赖,a.js执行依赖b.js文件,如果a.js先加载完,但是b.js还没有加载,就会存在问题。

          2.1.4:引入方式:动态异步的在某个时间点触发js的引入加载。

     2.2 懒加载和预加载:

          2.2.1:懒加载:即延迟加载资源,减少无效资源的加载,如电商类图片很多,页面很长,若没有进入用户可视区的图片,就暂时不用加载,进入用户可视区的图片才加载。包括原始js实现懒加载和第三方库zepto.lazyload.js实现懒加载

<!--
懒加载的原理:当图片出现在可视区内,就开始请求加载图片了。通过监听页面scroll,判断图片是否进入可视区,如果进入就开始懒加载该图片了
注意:1.图片一定要有固体的宽高尺寸大小,才能懒加载。
     2.初始化时,图片的src为空字符串。
     3.图片的url初始化要存在自定义属性中,如data-original。
-->
<!DOCTYPE html>
<html>
<head>        
    <meta charset="UTF-8">
    <title>1. 原生js实现懒加载</title>
    <style type="text/css">
    	.img-lists{
    		margin-left: 5%;
    	}
    	.pic{
    		width: 40%;
    		height: 400px;
    		margin-right: 5%;
    		display: inline-block;
    		height: 400px;
    		background: grey;
    	}
    </style>
</head>
<body>
	<div class="img-lists">
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
	</div>
    <script>    
       const heightVal=document.documentElement.clientHeight;  //获取可视区的高,即页面body的高。
       function lazyload(){
       	let ele=document.querySelectorAll('img[data-original][lazyload]');  //查找页面所有有[data-original][lazyload]这2个属性的图片标签。
       	Array.prototype.forEach.call(ele,function(item,index){
       		let obj; //声明变量用于存放当前图片的宽度尺寸数据
       		if(item.dataset.original ===''){
       			return
       		}else{
       			obj=item.getBoundingClientRect();  //获取当前图片的宽度尺寸数据
       		}
       		if(obj.bottom>=0 && obj.top<heightVal){ //如果当前图片在可视区之类
       			function(){
       				let img=new Image();
       				img.src=item.dataset.original;
       				img.οnlοad=function(){
       					item.src=img.src;
       				}
       				item.removeAttribute('data-original')
       				item.removeAttribute('lazyload')
       			}()
       		}
       	})
       }
        lazyload()
        window.addEventListener('scroll',lazyload)
    </script>
</body>
</html>
<!--
懒加载的原理:当图片出现在可视区内,就开始请求加载图片了。通过监听页面scroll,判断图片是否进入可视区,如果进入就开始懒加载该图片了
注意:1.图片一定要有固体的宽高尺寸大小,才能懒加载。
     2.初始化时,图片的src为空字符串。
     3.图片的url初始化要存在自定义属性中,如data-original。
-->
<!DOCTYPE html>
<html>
<head>        
    <meta charset="UTF-8">
    <title>2.zepto.lazyload.js实现懒加载</title>
    <style type="text/css">
    	.img-lists{
    		margin-left: 5%;
    	}
    	.pic{
    		width: 40%;
    		height: 400px;
    		margin-right: 5%;
    		display: inline-block;
    		height: 400px;
    		background: grey;
    	}
    </style>
</head>
<body>
	<div class="img-lists">
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
		<img src=""  class="pic" data-original='http://pic31.nipic.com/20130801/11604791_100539834000_2.jpg' lazyload='true'/>
	</div>
   <script src='./zepto.lazyload.js'></script>
   <script src='./zepto.min.js'></script>
   <script>
        $('img[data-original][lazyload]').lazyload()
   </script>
</body>
</html>

          2.2.2:预加载:即提前加载资源,然后在缓存中就可以直接使用资源,如H5项目中,提前加载好图片资源,以免切换动画效果时,出现空白情况。四种预加载的方式:

              ① 直接在img标签的src中加载:先隐藏该图片,需要的时候再显示,即可从缓存中读取该图片

<!--方式1:直接在img标签的src中加载,先隐藏该图片,需要的时候再显示,即可从缓存中读取该图片-->
<img src="http://pic41.nipic.com/20140508/18609517_112216473140_2.jpg" style='display:none'/>

              ② 使用Image对象加载:使用new Image() 事先加载好图片,在需要的时候,从缓存中获取。

//方式二:使用new Image() 事先加载好图片,在需要的时候,从缓存中获取
var img=new Image()
img.src="http://pic41.nipic.com/20140508/18609517_112216473140_2.jpg" 


document.getElementById('myimage').src=img.src;  //预先加载好图片,在需要的时候,从缓存中获取

               ③ XMLHttpRequest:可以更好处理图片请求的整个过程,但是存在跨域的问题。

var xml=new XMLHttpRequest();
xml.onreadystatechange=callback;
xml.οnprοgress=progressCallback;
xml.open('GET','http://pic41.nipic.com/20140508/18609517_112216473140_2.jpg',True);
xml.send();

function callback(){
    if(xml.readState==4 && xml.status==200){
        var text=xml.responseText;       
    }else{
        console.log('请求失败')
    }
}
function progressCallback(e){
    console.log(e)
    console.log('查看预加载的过程')
}

               ④ 使用第三方库PreloadJS实现预加载:

<script src='./preload.min.js'></script>
<script>
    var queue=new createjs.LoadQueue(false);   //参数为true时(类似xmlHttpRequest加载),存在跨域问题,为false时类似标签加载(img/new Image)。
    queue.on('complete',handleComplete,this);
    queue.loadManifest([
        {
            id:'myImage1',src='http://img.redocn.com/sheying/20150213/mulanweichangcaoyuanfengjing_3951976.jpg'
        },{
            id:'myImage2',src='http://img.redocn.com/sheying/20150213/mulanweichangcaoyuanfengjing_3951976.jpg'
        }
    ])

    function handleComplete(){
        var image=queue.getRequest('myImage1');
        document.body.appendChild(image);
    }
</script>

   

     2.3 浏览器的重绘与回流:

          2.3.1 css性能让js变慢?一个线程执行ui渲染,一个线程执行js,原本是2个分开的线程,理论上不应该相互阻塞,但是js可能获取或修改ui渲染的结果,就使得浏览器在执行js时,会冻结ui线程,在执行ui渲染时,会冻结js线程执行,从而形成阻塞。频繁的触发重绘与回流,会导致UI频繁渲染,最终导致js变慢。

          2.3.2 什么是回流:当render tree(dom树)中的一部分(或全部)因为元素的大小尺寸,布局位置,隐藏显示等改变而需要重新构建,即为回流。当页面布局和几何属性改变时,都会触发回流。一些元素的css会触发回流,如:盒子模型相关属性,定位属性及浮动,改变文字结构。

          2.3.3 什么是重绘:当render tree(dom树)中的一部分(或全部)元素的属性更新改变,从而影响元素的外观,风格,而不会影响大小布局的,即为重绘。一些元素的css会触发重绘,如背景颜色,字体颜色,内外边框,边框阴影等。

          2.3.4 解决重绘回流带来的性能问题方案:

              ① 避免使用触发重绘回流的css属性。
              ② 将重绘回流的影响范围限制在单独的图层内,video和canvas会自动创建图层。

              ③ 使用translate代替top:因为top/left/bottom/right.....的值变动会改变元素布局,引发回流重绘,而translate不会触发回流(即不会触发layout回流过程),只会触发重绘(paint)。
              ④ 使用opcaity替代visibility:因为visibility只会触发重绘,而opcaity不会(前提示该元素是单独独立出来的图层如添加transform:translateZ(0),或添加will-change:transform变成了图层) 。

              ⑤ 多使用class替代一天一天修改dom样式:因为每修改一条dom样式,会触发一次重绘。
              ⑥ 让dom离线后修改:如先把dom样式改为display:none(会触发一次回流),之后可以修改n次dom样式,不会触发回流重绘,再最后dom样式全部修改完在让其显示。

              ⑦ 不要让dom节点属性值放在一个循环里当成循环里的变量:如循环里获取offsetHeight/offsetWidth,会刷新缓存区,获取最新的dom的offsetHeight/offsetWidth,会触发回流,因为回流也有缓存机制。

              ⑧ 尽量不用table,可以用div替代:因为一个很小的改动会触发table重新布局,即触发回流重绘。

              ⑨ 动画的速度:动画效果会触发回流重绘,所以需要选择合适的动画速度。

              ⑩ 新增图层:图层里面的元素改变,只会触发当前图层的回流重绘,不会影响其他图层,chorme会根据性能自动新增图层。

              ⑾ 启动GPU硬件加速:合适的GPU加速,不易过多。

 

 


拓展:

     1、回流必定引起重绘,重绘不一定引起回流。

     2、将普通元素变成图层的方式:尽量在需要时候,添加图层,页面大量的图层会影响性能,因为图层合成上会花费很多时间。

           1.1 添加transform:translateZ(0)。

           1.2 添加will-change:transform。


 

 

 

3.结合服务端优化:

4.回顾总结:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章