HTTP和前后端分离
一、当用户在浏览器地址栏中输入网址,到看到页面,经历的步骤
tips:hexo自己搭一个博客
1.解析输入的URL地址
传输协议(把信息在客户端和服务器端进行传递,类似于快递小哥)
- http 超文本传输协议(传输的内容除了文本,还有可能是其它类型:二进制编码、BASE64码、文件流等等)
- https 比HTTP更加安全的传输协议(传输通道设置加密算法SSL),一般支付类网站都是HTTPS协议
- ftp 资源上传协议,一般应用于把本地文件直接上传到服务器端
域名 zhufengpeixun.cn
- 一级域名 www.zhufengpeixun.cn
- 二级域名 video.zhufengpeixun.cn
- 三级域名 webG.video.zhufengpeixun.cn
- 常用域名性质:.com国际 / .cn中国 / .gov政府 / .org官方 / .net系统 / .io博客 / .vip ...
端口号 (根据端口号,找到当前服务器上指定的服务)
- 0~65535之间
- 不同协议有自己默认的端口号(也就是自己不用写,浏览器会帮我们加上)
- http => 80
- https => 443
- ftp => 21
- 除这几个在书写的时候可以省略,其余的不能省
请求资源的路径和名称
- /stu/index.html
- 一般情况下,如果我们访问的是index.html等,可以省略不写(因为服务端一般会设置index.html为默认文档,当然可以自定义)
- 伪URL
- SEO优化 https://item.jd.com/100006038463.html
- 数据请求的接口地址 /user/list
- /stu/index.html
问号传参部分 ?xxx=xxx
- 客户端基于GET系列请求,把信息传递会服务器,一般都会基于问号传参的模式
- 页面之间跳转,信息的一些通信也可以基于问号传参的方式(单页面中组件和组件跳转之间的信息通信,也可能基于问号传参)
- 关于传递的内容需要进行编码处理(处理特殊字符和中文)
- encodeURI / decodeURI
- encodeURIComponent / decodeURIComponent
- escape / unescape
- ...
- encodeURI / decodeURI:只能把空格和中文内容进行编码和解码,所以一般应用这种模式处理整个URL的编码
- encodeURIComponent / decodeURIComponent:汇报所有的特殊字符和汉字都进行编码,一般不会整个URL编码,只会给传递的每一个参数值单独编码
- escape / unescape:这种方式不一定所有的后台都有,所以一般只应用于客户端自己内部编码,例如:存储cookie信息,把存储的中文进行编码和解码;特殊符号也会被编码;
设置哈希HASH #xxx
2.DNS解析
网站中,每发送一个TCP请求,都要进行DNS解析(一但当前域名解析过一次,浏览器一般会缓存解析记录,缓存时间一般在1分钟左右,后期发送的请求如果还是这个域名,则跳过解析步骤 =>这是一个性能优化点)
真实项目中,一个大型网站,他要请求的资源是分散到不同的服务器上的(每一个服务器都有自己的一个域名解析)
- WEB服务器(处理静态资源文件,例如:html/css/js等 的请求)
- 数据服务器(处理数据请求)
- 图片服务器 (处理图片请求)
- 音视频服务器
- ......
这样导致,我们需要解析的DNS会有很多次
优化技巧:DNS Prefetch 即 DNS 预获取
让页面加载(尤其是后期资源的加载)更顺畅更快一些
<meta http-equiv="x-dns-prefetch-control" content="on">
<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中可以看到
- 请求起始行
- 请求首部(请求头)
- 请求主体
强缓存 和 协商缓存(性能优化:减少HTTP请求的次数)
- 强缓存 ( Cache-Control 和 Expires )
- 协商缓存 ( Last-Modified 和 Etag )
5.服务器接受到请求,并进行处理,最后把信息返回给客户端
- HTTP响应报文(所有服务器返回给客户端的内容)
- 响应起始行
- 响应首部(响应头)
- date存储的是服务器的时间
- ...
- 响应主体
- 服务器返回的时候是:先把响应头信息返回,然后继续返回响应主体中的内容(需要的信息大部分都是基于响应主体返回的)
6.断开TCP链接通道 (四次挥手)
- 当客户端把请求信息发送给服务器的时候,就挥第一次手:客户端告诉服务器端,我已经把请求报文都给你了,你准备关闭吧
- 第二次挥手:由服务器发起,告诉浏览器,我接收完请求报文,我准备关闭,你也准备吧;
- 第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完毕,你准备关闭吧;
- 第四次挥手:由浏览器发起,告诉服务器,我响应报文接收完毕,我准备关闭,你也准备吧;
Connection: Keep-Alive 保持TCP不中断(性能优化点,减少每一次请求还需要重新建立链接通道的时间)
7.客户端渲染服务器返回的结果
二、前端性能优化点
1. 减少HTTP请求的次数和大小
- 合并压缩 webpack(代码比较少的情况下,尽可能使用内嵌式)
- 雪碧图或者图片BASE64
- 尽量使用字体图标
- 对于动态获取的图片,采用图片懒加载(数据也做异步分批加载:开始只请求加载第一屏的数据,滑动到第几屏在加载这一屏的数据和图片)
- 骨架屏技术(首屏内容由服务器渲染;再或者开始展示占位结构,客户端在单独获取数据渲染;)
- 音视频取消预加载(播放的时候再去加载音视频文件,对于自动播放采取延迟播放的处理)
- 服务器采用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 来异步加载
……
4.安全优化
5.webpack上的优化
三、AJAX基础知识
AJAX:async javascript and xml 异步的JS和XML
XML(最早的时候,基于AJAX从服务器获取的数据一般都是XML格式数据,只不过现在基本上都是应用更小巧、更方便操作的JSON格式处理)
- HTML 超文本标记语言
- XHTML 严谨的HTML
- XML 可扩展的标记语言(基于标签结构存储数据)
异步的JS(基于AJAX实现局部刷新)
- 服务器渲染(一般都是同步:全局刷新) 全靠服务器解析数据和界面,压力大
- 客户端渲染(一般都是异步:局部刷新)
发送AJAX请求有四步操作
1.创建一个XHR对象
- 不兼容XMLHttpRequest的浏览器使用ActiveXObject创建
2.打开请求连接(配置请求信息)
- xhr.open([METHOD],[URL],[ASYNC],[USER-NAME],[USE-PASS])
- [METHOD]请求方式
- GET系列:从服务器获取 POST系列:向服务器发送信息
- [ASYNC]是否为异步请求,默认是true,也就是异步,设置为false代表当前请求任务为同步(项目中基本上都采用异步请求)
- [USER-NAME],[USE-PASS] 向服务器发送请求所携带的用户名密码,只有在服务器设置了安全来宾账号的情况下需要(一般不用)
3.监听请求状态,在不同状态中做不同的事情
4.发送AJAX请求(AJAX任务开始,直到响应主体信息返回[AJAX状态为4]代表当前任务结束)
GET系列 VS POST系列
let xhr = new XMLHttpRequest;
xhr.open('get', './data.json?lx=1&name=zhufeng');
xhr.send();
xhr = new XMLHttpRequest;
xhr.open('post', './data.json');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('lx=1&name=zhufeng&obj=' + encodeURIComponent(JSON.stringify({
name: '哈哈'
})));
xhr = new XMLHttpRequest;
xhr.open('post', './data.json');
xhr.setRequestHeader('Content-Type', 'multipart/form-data');
let formData = new FormData();
formData.append('lx', 2);
formData.append('name', 'zhufeng');
formData.append('obj', {
name: '哈哈'
});
xhr.send(formData);
传递给服务器的数据格式:
- application/x-www-form-urlencoded:xxx=xxx&xxx=xxx (最常用的方式) 【字符串】
- multipart/form-data (也很常用,例如:表单提交或者文件上传) 【对象】
- raw (可以上传text、json、xml、html等格式的文本,富文本编辑器中的内容可以基于这种格式传递)
- binary (上传二进制数据或者编码格式的数据)
GET系列 VS POST系列
- 不管是哪一种请求方式,客户端都可以把信息传递给服务器,服务器也可以把信息返回给客户端,只不过GET偏向于拿(给的少拿的多),而POST偏向于给(给的多拿的少)
- [GET系列]:GET / HEAD(只获取响应头的信息,不获取响应主体内容)/
- DELETE(删除,一般代指删除服务器上指定的文件)
- OPTIONS(试探性请求,在CROSS跨域请求中,所以正常请求发送前,先发送一个试探请求,验证是否可以和服务器正常的建立连接)
- [POST系列]:POST / PUT(新增,一般代指向服务器中新增文件)
基于GET向服务器发送请求,传递给服务器的方式:
- 基于请求头传递给服务器(比如想把本地的Cookie信息传递给服务器)
- 请求URL地址后面的问号传参(主要方式) xhr.open('get', './data.json?id=2&lx=0')
基于POST向服务器发送请求,传递给服务器的方式:
- 基于请求头传递给服务器
- 基于请求主体,把信息传递给服务器(主要方式) xhr.open('post', './data.json'); xhr.send(data);
get和post的区别
GET请求传递给服务器的信息有大小的限制(因为它是基于地址问号传参方式传递信息,而URL有长度的限制:IE浏览器只有2KB大小...);而POST请求理论上是没有大小限制的(实际操作中也都会给予限制);
GET请求相对POST请求来说不太安全,也是因为传参是基于地址栏问号传参,会被别人基于URL劫持的方式把信息获取到...所以真实项目中,涉及到安全的信息(例如:密码等)都是基于POST方式传递的(互联网面前人人都在裸奔,没有绝对的安全,我们需要更多的处理安全性)
GET请求容易产生缓存,原因还是因为GET是基于问号传参传递信息的,浏览器在每一次获取数据后,一般会缓存一下数据,下一次如果请求的地址和参数和上一次一样,浏览器直接获取缓存中的数据,所以我们基于GET发送请求,需要清除缓存的时候,一般都会在地址栏中添加一个随机数
xhr.open('get', './data.json?lx=1&name=zhufeng&_='+Math.random())AJAX状态码
0 =>unset
1 =>opened
2 => headers_Received
3 => loading
4 => done
let xhr = new XMLHttpRequest();
xhr.open("get", "./data.json");
// xhr.timeout = 100; 设置超时时间
// xhr.withCredentials=true; 跨域资源共享中,允许携带资源凭证
// xhr.abort() 强制中断AJAX请求
// xhr.setRequestHeader() 设置请求头信息(记住:属性值不能是中文和特殊字符)
xhr.setRequestHeader("name", encodeURIComponent("hello"));
xhr.onreadystatechange = function () {
let status = xhr.status,
state = xhr.readyState,
result = null;
if (!/^(2|3)\d{2}$/.test(status)) {
// 错误处理
return;
}
// AJAX状态码为2的时候,响应头信息回来了
if (state === 2) {
// 获取响应头信息
console.log(xhr.getAllResponseHeaders());
// console.log(xhr.getResponseHeader('date')); //=>获取的服务器日期是格林尼治时间 GMT(比北京时间晚了八个小时 北京时间:GMT+0800)
console.log(new Date(xhr.getResponseHeader("date"))); //=>转换为北京时间
return;
}
if (state === 4) {
// 获取响应主体信息 responseText/responseType/responseXML
result = xhr.response;
console.log(result);
}
};
xhr.send();
/* SEND后:首先响应头信息回来 最后响应主体信息再回来 */
HTTP状态码
状态码:AJAX状态码 \ 服务器返回的HTTP网络状态码(代表了服务器返回信息的状态)
[2开头的基本都是代表成功]
+200 OK 正常返回数据[3开头的一般也是成功了,只不过中间做了一些额外处理]
- 301 Moved Permanently 永久性转移/重定向 一般应用于网站域名更换,访问老域名,永久都跳转到新的域名上
- 302 Move Temporarily 临时转移
- 307 Temporary Redirect 临时重定向 一般应用于服务器的负载均衡
- 304 Not Modified 读取的是缓存中的数据 这个是客户端和服务器端共建的协商缓存(把不经常更新,请求过的资源文件做缓存,后期在访问这些资源直接走缓存数据,除非服务器端更新了此资源,或者客户端强制清缓存刷新等)
[4开头的都是失败:失败的原因一般都是客户端的问题]
- 400 Bad Request 请求参数错误
- 401 Unauthorized 无权限访问
- 404 Not Found 地址错误
- 405 Method Not Allowed 当前请求的方式服务器不支持
[5开头的都是失败:失败的原因一般都是服务器问题]
- 500 Internal Server Error 未知服务器错误
- 503 Service Unavailable 服务器超负荷
常用的AJAX库(调用起来更方便、操作起来更简单):
以JQ/ZEPTO为主的AJAX库(把AJAX四步操作进行封装、提供了JSONP跨域处理、对于一些常用操作[例如:GET/POST/表单序列化/参数处理等]进行封装处理)
以AXIOS为主的AJAX库(不仅对AJAX进行封装,而且是基于PROMISE进行管理) [最常用的]
ES6中新增的内置类Fetch来完成HTTP数据请求(记住:FETCH不是AJAX,他是新的通讯方案;而且默认基于PROMISE进行管理) [很多公司已经开始尝试使用]
跨域请求方案...
AXIOS
axios.request({
// 请求的地址 baseURL:统一基本地址,一般是设置地址的前缀
url: "./data.json",
// 请求的方式
method: "GET",
// 设置请求头信息
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
// GET请求下,问号传参的信息
params: {},
// POST请求下,请求主体传参的信息
data: {},
})
.then((result) => {
// 从服务器获取的结果
/*
* config:你自己的配置信息
* headers:响应头信息
* request:原生的XHR AJAX对象
* status:网络状态码
* statusText
* data:存储的是响应主体信息
*/
console.log(result);
});
- 快捷请求的方法 axios.get/delete/head/post/put... 或者 axios.all
.get([URL],[OPTIONS])
axios.get("./data.json", {
params: {
lx: 0,
},
/* transformResponse: [function (data) {
// 把从服务器获取的请求主体信息进行处理,处理完在执行THEN操作
}] */
});
// .get([URL],[DATA],[OPTIONS]) [DATA]是基于请求主体传递的内容
// axios基于请求主体传递给服务器的默认是JSON格式,如果想变为x-www-form-urlencoded格式还需要自己处理
axios.post('./data.json', {
lx: 0,
name: 'zhufeng'
}, {
// 针对于POST系列请求,把传递给服务器请求主体内容进行处理(函数中返回的结果就是最后基于请求主体传递给服务器的内容)
transformRequest: [function (data) {
// xxx=xxx&xxx=xxx
let str = ``;
for (let key in data) {
if (!data.hasOwnProperty(key)) break;
str += `&${key}=${data[key]}`;
}
str = str.substring(1);
return str;
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});