我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
当用户在浏览器地址栏中输入网址,到看到页面,经历的步骤有那些?
1.解析输入的URL地址
-
传输协议(把信息在客户端和服务器端进行传递,类似于快递小哥)
-
- http 超文本传输协议(传递的内容除了文本,还有可能是其他类型,二进制编码、BASE64码、文件流等等)
- https 比HTTP更加安全的传输协议(传输通道设置加密算法SSL),一般支付类网站都是HTTPS协议
- ftp 资源上传协议,一般应用于把文件直接上传到服务器端
-
域名 baidu.com
-
-
一级域名 www.baidu.com
- 二级域名 image.baidu.com
-
三级域名 www.image.baidu.com
-
常用域名性质: .com国际 / .cn中国 / .gov政府 / .org官方 / .net系统 / .io博客 / .vip ....
-
-
端口号(根据端口号:找到当前服务器上指定的服务)
-
- 0-65535之间
- 不同协议有自己默认的端口号(也就是自己不用写,浏览器会帮我们加上)
-
- http => 80
- https => 443
- ftp => 21
- 除这几个在书写的时候可以省略,其余的不能省
-
请求资源的路径和名称
-
/index.html
- 一般情况下,如果我们访问的是index.html等,可以省略不写(因为服务端一般会设置index.html为默认文档,当然也可以自定义)
- 伪URL
- SEO优化
- 数据请求的接口地址 /user/list
//=> 数据请求的接口地址 /user/list
静态地址
https://item.jd.com/10789.html(搜索引擎抓取)
动态页面地址:真实地址
https://item.jd.com/detail.php?id=10789
需要把这样的地址重写为上述的静态地址(URL伪重写技术)
-
问号传参部分 ?xxx=xxx
-
客户端基于GET系列请求,把信息传递回服务器,一般都会基于问号传参的模式
- 页面之间跳转,信息的一些通信也可以基于问号传参的方式(单页面组件和组件跳转之间的信息通信,也可能基于问号传参)
- 关于传递的内容需要进行编码处理(处理特殊字符和中文)
-
- encodeURI / decodeURI
- encodeURIComponent / decodeURIComponent
- escape / unescape
- ....
/*
* encodeURI / decodeURI:只能把空格和中文内容进行编码和解码,所以一般应用于这种
模式处理整个URL的编码
* encodeURIComponent / decodeURIComponent:会把所有的特殊字符和汉字都进行编码
和解码,一般不会整个URL编码,只会给传递的每一个参数值单独编码
*
* =>客户端和服务器端都支持这两种编码和解码方式(客户端和服务器端信息编码一般都是
基于这种方式)
*/
let str = `https://www.baidu.com?lx=aa&url=${encodeURIComponent('htt
ps://www.jd.com')}&name=${encodeURIComponent("买买买")}`;
console.log(str);
// ========= ========= ========= ========= ========= ========= =========
let str ='https://www.baidu.com?lx=aa&url=https://www.baidu.com&name
="买买买"';
console.log(encodeURI(str));
//=> https://www.baidu.com?lx=aa&url=https://www.baidu.com&name=%22%E4%B9%B0%E4%B9%B0%E4%B9%B0%22
// ========= ========= ========= ========= ========= ========= =========
/*
* escape / unescape:这种方式不一定所有的后台都有,所以一般只应用于客户端自己
内部编码,例如:存储cookie信息,把存储的中文进行编码和解码;特殊符号也会被编码;
*/
console.log(escape("买买买")); //=> '%u4E70%u4E70%u4E70'
console.log(unescape("'%u4E70%u4E70%u4E70'")); //=> '买买买'
一次完整的HTTP事务
1. 建立起TCP/HTTP传输通道(客户端和服务器端)
2. 客户端正常发送请求
3. 服务器端正常把信息返回
4. 断开TCP/HTTP传输通道
- 设置哈希HASH #xxx
2. DNS解析
-
网站中,每发送一个TCP请求,都要进行DNS解析(一旦当前域名解析过一次,浏览器一般会缓存解析记录,缓存时间一般在1分钟左右,后期发送的请求如果还是这个域名,则跳过解析步骤 =>这是一个性能优化点)
-
真实项目中,一个大型网站,它要请求的资源是分散到不同的服务器上的(每一个服务器都会有自己的一个域名解析)
-
WEB 服务器(处理静态资源文件,例如:等html/css/js的请求)
- 数据服务器(处理数据请求)
- 图片服务器(处理图片请求)
- 音视频服务器
- .....
- 这样导致,我们需要解析的DNS会有很多次
-
优化技巧:DNS Prefetch 即 DNS 预获取
让页面加载(尤其是后期资源的加载)更顺畅更快一些
<link rel="dns-prefetch" href="//static.360buyimg.com">
<link rel="dns-prefetch" href="//misc.360buyimg.com">
<link rel="dns-prefetch" href="//img10.360buyimg.com">
<link rel="dns-prefetch" href="//img11.360buyimg.com">
<link rel="dns-prefetch" href="//img12.360buyimg.com">
3.基于TCP的三次握手,构建客户端和服务器端的连接通道
- 只有建立好连接通道,才能基于HTTP等传输协议,实现客户端和服务器端的信息交互
- 第一次握手:由浏览器发起,告诉服务器我要发送请求了
- 第二次握手:由服务器发起,告诉浏览器我准备接收了,你赶紧发吧
- 第三次握手:由浏览器发送,告诉服务器,我马上就发了,准备接收吧
4.发送HTTP请求
基于HTTP等传输协议,客户端把一些信息传递给服务器
- HTTP请求报文(所有客户端传递给服务器的内容,统称为请求报文)
-
- 谷歌控制台NetWork可以查看
- 请求起始行
- 请求首部(请求头)
- 请求主体
- 强缓存 和 304协商缓存(性能优化:减少HTTP请求的次数)
-
- 强缓存(Cache-Contorol 和 Expires)
- 304协商缓存(Last-Modified 和 Etag)
5. 服务器接收到请求,并进行处理,最后把信息返回给客户端
- HTTP响应报文(所有服务器返回给客户端的内容)
-
- 响应起始行
- 响应首部(响应头)
-
- data存储的是服务器的时间
- ...
- 响应主体
- 服务器返回的时候是:先把响应头信息返回,然后继续返回响应主体中的内容(需要的信息大部分都是基于响应主体返回的)
6.断开TCP链接通道(四次挥手)
- 第一次挥手:由浏览器发起,发送给服务器,我已经把请求报文都给你了,你准备关闭吧
- 第二次挥手:由服务器发起,告诉浏览器,我接收完成请求报文,我准备关闭,你也准备吧
- 第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完毕,你准备关闭吧
- 第四次挥手:由浏览器发起,告诉服务器,我响应报文接收完毕,我准备关闭,你也准备吧
7.客户端渲染服务器返回来的结果
Connection:keep-Alive 服务器端设置
- 保持TCP不中断(性能优化点:减少每一次请求还需要重新建立链接通道的时间)
浏览器的渲染及优化
- 在真实项目中,如果CSS样式代码不是很多的(再或者是移动端的项目),我们应该使用内嵌式,以此来减少HTTP资源的请求,提升页面渲染的速度
- 网络资源请求或HTTP请求的最大并发数
- 大部分浏览器都维持在七个左右(为了避免并发的上限,导致某些资源要延迟加载,页面渲染速度变慢,我们应该尽量可能减少HTTP的请求数量)
- 现代浏览器都有完善的代码扫描机制:如果遇到script需要同步加载和渲染代码,浏览器会在渲染JS的同时向下继续扫描代码,如果发现有一些异步的资源代码,此时就开始向服务器端加载请求了
- 在JS中还有可能操作元素的样式,所以哪怕都是异步请求资源的情况下,JS先加载回来,也要等到它之前发送的CSS加载并渲染完成后才会执行JS代码
页面加载的过程中遇到JS(JS中会有操作DOM的代码):
【默认script src = 'xxx'】
主线程会从服务器获取到JS资源,并且把JS资源进行解析加载,加载完成后再继续渲染DOM结构
【如果设置了defer或者async】
都是让其变为异步获取资源(不会阻碍DOM的渲染)
//=> defer可以遵循原有的加载顺序,获取后按照顺序去依次渲染JS
//=> async无序的(谁先获取到先执行谁)
为啥要把link写在结构的上面,把script写在结构的下面?
//=> link放到顶部是为了更快加载回来的CSS
//=> script放到底部是为了获取DOM元素或者不阻碍DOM的渲染
DOMContenLoaded 事件:当DOM结构加载完成就会触发
//=> DOM树有了,并且JS也执行加载了,此时触发这个事件
load 事件:当所有资源都加载完成才会触发
//=> 包含了需要等待图片等资源也都加载完成触发
JQ类库中:$(fucntion(){}) 或者 $(docunemt).ready(function(){})
//=> 当DOM结构加载完成才会执行函数中的代码
//=> 原理就是应用DOMContenLoaded事件完成的
//=> DOMContenLoaded在低版本浏览器中不兼容,不兼容使用onreadystatechange事件代替在这个事件中监听document.readyState值,只为complete代表DOM结构加载完成
浏览器中DOM的重绘与回流
- 重绘(Repaint):元素样式的改变(但是宽高、大小、位置不变)
- 回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局导致渲染树重新计算布局和渲染
注意:回流一定会触发重绘,而重绘不一定触发回流
避免DOM的回流
- 放弃传统操作dom的时代,基于Vue/react开始数据影响视图模式
- 分离读写操作(现代的浏览器都是有渲染队列的机制)
- 动画效果应用到position属性absolute或flxed的元素上(脱离文档流)
- CSS3硬件加速(GPU加速 :所有触发硬件加速的东西都不会引发回流和重绘)
-
- 例如:transform / opacity / filters ...这些属性会触发硬件加速,不会引发回流和重绘。可能会引发的坑:过多的使用会占用大量内存,性能消耗严重,有时候会导致字体模糊
- 牺牲平滑度换取速度
- 避免table布局和使用CSS的JavaScript表达式
注意:能用CSS3动画实现的就不用JS做动画这样可以减少回流
window.addEventListener('DOMContenLoaded', _=> {
let navBox = document.getElementById('navBox');
//=>渲染队列机制导致引发一次回流
navBox.style.width = '100px';
navBox.style.height ='100px';
//触发两次
navBox.style.width = '100px';
console.log(navBox.clientWidth); //clientWidth获取盒子的宽度
navBox.style.hdight ='100px';
//=>10次回流
for (let i = 0; i < 10; i++) {
let span = docuement.createElement('span'); //创建一个元素对象
navBox.appendChild(span);
}
//一次回流
let frag = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
let span = docuement.createElement('span');
frag.appendChild(span);
}
navBox.appendChild(frag);
//可以基于ES6的模板字符串
let str = ``;
for (let i = 0; i < 10; i++) {
str += `<span></span>`;
}
navBox.innerHTML = str;
})
前端性能优化总结
1. 减少HTTP请求次数和请求的大小
- 文件合并压缩(代码偏少的情况下,尽可能使用内嵌式)
- 雪碧图 CSS sprice 或 图片base64
- 使用字体图标
- 对于动态获取的图片,采用图片懒加载(数据也做异步分批加载,开始只请求加载第一屏的数据,滑动到第几屏在加载这一屏的数据和图片)
- 骨架屏技术(首屏内容由服务器渲染、在或者开始展示展位结构,客户端再单独获取数据渲染)
- 音视频取消预加载(播放的时候再去加载音视频文件,对于自动播放采取延迟播放的处理)
- 在客户端和服务器端进行信息交互的时候,对于多项数据我们尽量可能基于JSON格式来进行传输(JSON格式的数据处理方便,资源偏小)
- 开启服务器端的gzip压缩
2. 缓存机制
把一些请求回来的信息进行本地存储(缓存存储),在缓存有效期内,再次请求资源,直接从缓存中获取数据,而不是服务器上从新拉取
- DNS预获取
- 资源文件的强缓存和协商缓存(304)
- 数据也可以做缓存(把从服务器获取的数据存储到本地:cookie / localstorage / redux / vuex 等,设定期限,在期限内,直接从本地获取数据即可)
- 离线存储(一般很少用)manifest
- CDN区域分布式服务器开发部署(费钱 效果非常好)
3. 代码上的优化
- 减少DOM的重绘回流
- 在JS中尽量减少闭包的使用(内存优化)
- 在JS中避免“嵌套循环”和“死循环”
- 尽可能使用事件委托
- 尽可能减少CSS表达式的使用(expression)
- CSS选择器解析规则是从右向左解析(基于less / sass开发的时候尽可能减少层级嵌套,目的让选择器的前缀短一点)【 a{ } 和 .box a { }】
- 尽可能实现JS的封装(低耦合高内聚),减少页面代码的冗余代码
- 在CSS导入的时候尽量减少使用@import导入式
- 使用window.requestAnimationFrame(JS中的帧动画)代替传统的定时器动画(能用CSS3中的动画绝不用JS中的动画)
- 减少递归的使用,避免死递归,避免由于递归导致的栈内存嵌套
- 基于SCRIPT调取JS的时候,可以使用 defer 或者 async 来异步加载
- 避免使用while语句
- .....