前后端数据交互
-
概念
-
前端和后端数据交互的过程
-
浏览器和服务器之间数据交互的过程
-
后端向前端发送数据
- 访问页面
-
前端向后端发送数据
- 注册用户
-
附:cli 查看网页工具
-
curl xxx.com
-
-
-
前后端通信方式
- 浏览器访问网页
- HTML 标签
- link / img / script / iframe
- 浏览器在解析HTML 代码时,遇到这几种标签会向服务器再次发送请求,获取相应的资源
- 像这种资源请求,不是串行进行的,像chrome 浏览器最多可以六个一起请求,这里的六个指的是同一个域名下,同一个域名下一共最多可以有六个并发请求,可以把图片放到不同域名下,使请求更快,一些小图片放到一张大图里
- a / form
- 还有一些标签,用户使用它们向服务器发送请求
- Ajax 和 Fetch
HTTP协议
超文本传输协议
- 请求相应的过程
-
先解析ip,本地有就直接用,没有就用DNS 解析,然后看缓存中有没有资源,本地有就直接用,没有就建立TCP 通信,有些缓存可能存在过期或者信息不全的情况,建立TCP 连接
-
-
-
有大小的,是新请求的,memory cache 内存缓存,disk cache硬盘缓存,这些内容都是后端设置的
-
- HTTP 报文
-
-
get 就是没有请求体的,数据在请求头中
-
- HTTP 方法
- 常用的HTTP 方法
- 方法的语义
- GET 获取数据
- POST 创建数据
- PUT 更新数据
- DELETE 删除数据
- 增删改查
- RESTful 接口设计(主要是后端的内容)
- 一种接口设计风格,充分利用HTTP 方法的语义
- 一般来说,增删改查可以只使用GET POST 两种方法,就需要设计四个接口
- RESTful 方式则可以用同一个接口就可以了,四种方法来区分结果
- GET POST 区别
- 语义不同
- 发送数据
- GET 通过地址在请求头中携带数据,因此能携带的数据量和地址的长度有关系,一般最多就几k ,
- POST 也可以在请求头中携带数据,但是一般在请求体中携带
- 缓存
- GET 可以被缓存,POST 不会被缓存,因为GET 的数据是和地址一起访问的,而地址会被缓存,POST 数据在请求体中,都缓存是不可能的,太多太占地方了
- 安全性
- 其实都是半斤八两,一样不太安全
- 发送密码或其他敏感信息时不要使用GET ,主要是避免直接被他人窥屏或通过历史记录(缓存)找到你的密码,从这一点看,POST 比GET 安全一点
- 状态码
- 服务器对请求的处理结果,是服务器返回的
- 状态码语义
- 1xx:代表请求已被接受,需要继续处理(websocket【live server 插件原理】)
- 2xx:成功
- 3xx:重定向
- 301 【Moved Permanently】:使用时需要谨慎,因为用户如果有缓存了,以后就一直向已经缓存的地址跳转了,从硬盘缓存中读取地址
- 302 【Move Temporarily】:不会缓存,每一次向服务器发送请求确认向哪跳转
- 304 【Not Modified】:本地有一份缓存,浏览器向服务器确定本地的缓存没有过期,服务器返回304 就表示没有过期
- 4xx:请求错误
- 400 【Bad Request】
- 404 【Not Found】
- 5xx:服务器错误
- 500 【Internal Server Error】
本地存储
-
Cookie
-
概念
- 浏览器存储数据的一种方式
- 因为存储在用户本地,而不是服务器上,是本地存储
- 一般会自动随着浏览器的请求发送到服务器端
-
作用
- 利用Cookie 跟踪统计用户访问该网站的习惯,比如什么时间访问,访问了哪些页面,在每个网页的停留时间等
-
浏览器中访问 cookie
-
在请求的请求头中的Cookie 字段中
-
document.cookie // 分号空格隔开
-
不要把敏感信息放到Cookie 中
-
-
基本用法
-
写入 cookie
document.cookie = 'name=alex' document.cookie = 'age=1' // 不能一起设置,只能一个一个设置,下面不会覆盖上面
-
读取 cookie
读出来的是字符串,每一对分号空格隔开,只能一次性读取出来,不能一个一个读,后面会进行封装
-
-
属性
-
Name 和 Value 是必须的,其他都不是,其他可以使用默认值
-
名称 或 值,如果包含非英文,则写入时需要使用 encodeURIComponent() 编码,读取时使用 decodeURIComponent() 解码
-
document.cookie = `name=${encodeURIComponent('张三')}`
-
-
失效时间
-
失效的Cookie ,会被浏览器清除
-
如果没有设置失效时间,这样的Cookie 称为会话Cookie,(默认 Session)
-
它存在内存中,当会话结束,也就是浏览器关闭时(不是关闭某个标签页),Cookie 消失
-
想长时间存在,设置Expires 或者 Max-Age
-
// 值是 Date 对象 document.cookie = `name=alex; expires=${new Date('2100-1-01')}` // 值是数字,表示多少秒之后过期,通过计算可以设置天 document.cookie = `name=alex; max-age=${24 * 3600 *30}` // 如果 Max-Age 的值是0 或者负数,Cookie 会被删掉
-
-
-
Domain 域
-
限定了访问 Cookie 的范围
-
把访问范围用域名区分开来,Domain 默认值就是域名
-
在当前网页下,只能设置当前的域名,换句话说:
-
使用JS 只能读写当前域或父域的Cookie,无法读写其他域的 Cookie
-
document.cookie = `name=alex; domain=xxx` // www.imooc.com m.imooc.com // 的父域: .imooc.com
-
-
Path 路径
- 同样限定了Cookie 的访问范围 (同一个域名下)
- 默认 / 也就是根目录
- 在下层目录可以设置上层路径,在上层不能设置下层的,读写都是这样
-
HttpOnly
- 设置了 HttpOnly 属性的Cookie 不能通过JS 去访问,为了安全性考虑
-
Secure 安全标志
- 限定了只有在使用了https 而不是http 的情况下才可以发送给服务端
- 注意:
- 只有name、domain、path 都相同才是一个Cookie
- Domain、Path、Secure 都满足条件,并且不能过期的Cookie 才会随着请求发送到服务器
-
-
-
封装——读写删
-
const set = (name, value, {maxAge, domain, path, secure}={}) => { let cookieText = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; if(typeof maxAge === 'number') cookieText += `; max-age=${maxAge}`; if(domain) cookieText += `; domain=${domain}`; if(path) cookieText += `; path=${path}`; if(secure) cookieText += `; secure`; document.cookie = cookieText; } const get = name => { name = `${encodeURIComponent(name)}` const cookies = document.cookie.split('; '); for(const item of cookies){ const [cookieName, cookieValue] = item.split('='); if(cookieName == name) return decodeURIComponent(cookieValue); } return; } const remove = (name,{ domain, path} = {}) => { set(name, '', { domain, path, maxAge:-1}); } export { set, get,remove };
-
注意:读的时候放心用等号 = 分隔,因为如果name 或 value 中有等号,会被编码
-
-
注意事项
- 前后端都可以写入和获取Cookie
- Cookie 有数量限制
- 每个域名下有数量限制,看浏览器和版本
- 超过限制,会清除以前设置的Cookie
- Cookie 有大小限制
- 不要存太多就行了
localStorage
-
概念
- 有一些信息需要存储在本地,但是不需要发送到服务器端,这时就可以使用localStorage 存储
- 单个域名下的localStorage 总大小有限制,不同浏览器不同,一般不会存满
-
基本用法
-
localStorage.setItem('name', 'alex'); console.log(localStorage); localStorage.getItem('name'); // 不存在的返回 null localStorage.removeItem('name'); // 删除不存在的Item 不会报错 localStorage.clear();
-
使用 localStorage 自动填充
-
-
注意
- 存储期限
- 只有键和值,没有其他属性
- localStorage 是持久化的本地存储,除非手动清除(js 删除,或者清除浏览器缓存),否则永远不会过期
- 注意清除
- SessionStorage
- 当会话结束(比如关闭浏览器),SessionStorage 数据会被清空
- 其他用法和 localStorage 几乎完全一样
- liveServer 也会使用它存一个值
- 键和值的类型
- 只能是字符串类型,如果不是也会转成字符串再存
- 不同域名下能否共用 localStorage
- 不能
- 兼容性
- IE7 以下不支持
- https://caniuse.com/
- 存储期限
Ajax&Fetch 与跨域请求
Ajax
-
初识
- Asynchronous JavaScript and XML 的缩写
- 异步的向服务器发送请求,在等待的过程中,不会阻塞当前页面,直到成功获取相应后,浏览器才开始处理相应数据
- XML (可扩展标记语言)
- 是一种前后端数据通信时传输数据的一种格式,很像html
- 现在已经不怎么用了,现在常用的是 JSON
- 使用Ajax 可以在不重新加载整个页面的情况下,对页面的某部分进行更新
- 应用例子:
- 注册检测,手机号如果已经被注册过,页面会直接提示换号码
- 搜索提示,在搜索框输入内容,动态提示相关搜索
-
搭建Ajax 开发环境
- Ajax 必须使用服务器环境(前面必须是http/https 协议)
- 直接用live server 就行了
- 其他推荐:windows phpstudy、Mac MAMP
-
基本用法
-
XMLHTTPRequest
-
Ajax 想要实现浏览器与服务器之间的异步通信,需要依靠 XMLHTTPRequest ,它是一个构造函数
-
注意: 不论是 XMLHTTPRequest 还是 Ajax 都没有具体和某种数据格式绑定
-
// 1. 创建 xhr 对象 const xhr = new XMLHTTPRequest(); // 之后就是使用这个对象的方法了 // 2. 准备发送请求: xhr.open(); // open 参数: HTTP 方法、地址 URL、是否异步(一般都用异步 true)、 // 3. 发送请求 xhr.send(); // send 参数 是通过请求体携带的数据,因此 GET 不能用,POST 使用 // xhr.send(null); // 4. 监听事件,处理响应 (这一步最好写在发送前面) (一般不用这个,用后面 load 事件) xhr.onreadystatechange = () => {}; // readystate 改变会触发事件,0~4 状态: // 0:未初始化,未调用 open // 1:启动,调用了 open ,未调用 send // 2:发送,调用了 send ,未收到响应 // 3:接收,已经接收部分响应 // 4:完成,接收了全部响应,并可以在浏览器中使用(主要) // 获取到响应后,响应的内容会自动填充 xhr 对象的属性 // 5. 响应状态码:xhr.status 响应码说明: xhr.statusText xhr.onreadystatechange = () => { if(xhr.readystate != 4) return; if((xhr.status >= 200) & (xhr.status < 300) || xhr.status === 304){ console.log( xhr.responseText; ); } }; // 其他: // readystatechange 事件也可以使用 addEventLisener // 事件中也可以使用 this 代替 xhr 对象
-
-
-
GET 请求
- 携带数据
-
GET 请求不能通过请求体携带数据,但可以通过请求头携带,这里的XHR 就表示上面 xhr 对象方式
-
-
from 表单也是类似原理,把参数拼接到 url 上面提交,和GET 一样
-
- 数据编码
- 非英编码,还是 encodeURIComponent 和 decodeURIComponent
- 携带数据
-
POST 请求
-
携带数据
-
请求体和请求头都可以携带
-
// 1. 可以直接写到 send 参数位置 xhr.send('username'); // 但是一般不会这样传,需要按照数据格式传,以前是用 xml 格式,现在一般用两种格式: // Form-Data 表单格式,和表单一样,后端就不需要考虑区分 (注意:格式在后面介绍) xhr.send('username=alex&age=1'); // 不能直接传递对象,需要先将对象转成字符串形式,下面这个结果是: [object Object] xhr.send({ username: 'alex', age: 1 }); // 这里直接传,相当于传字符串,而对象转成字符串是 [object Object] ,使用JSON 格式就不一样了,使用 JSON 就是传 JSON 格式数据,而不是字符串,默认是字符串的意思
-
-
数据编码
- 非英编码,还是 encodeURIComponent 和 decodeURIComponent
-
注意:
- 请求数据可以分为两种:FormData 以及 Request Payload
- FormData 就是键值对形式,也就是字符串,Request Payload 就是POST 请求体发送的数据,分两种,一个是键值对形式,另一个是 json 格式,键值对那种,在开发者工具中还是展示在 Form Data 栏下面,只有json 格式的会出现在 Request Payload 栏下面,不过这都不是很重要,json 的方式需要设置 'Content-Type' 字段(后面xhr 属性中有)
-
JSON
-
是什么
-
xhr.responseText 返回的就是一种 JSON 格式(注意,不是字符串),JSON 有三种表现形式,可以和JS 数据类型互相转换
-
把对象字符串化,并且保留对象格式
-
Javascript Object Notation(JS 对象表示法)
-
比 XML 更好的格式
-
-
三种形式
-
简单值形式
-
注意:xhr 不仅可以获取远程的数据,也可以获取本地的数据
-
xhr.open('GET', './plain.json', true);
-
JSON 中不支持undefined ,其他基本数据类型都有
-
基本数据类型直接写,字符串必须双引号包
-
JSON 中不能写注释
-
-
对象形式
- 字符串必须加双引号
- 不支持undefined
-
数组形式
- 和上面类似
-
-
常用方法
- JSON.parse()
- 将JSON 格式的字符串解析成 JS 中对应的值
- JSON.stringify()
- 字符串化,转成JSON 格式
- 封装 localStorage
- JSON.parse()
跨域
-
是什么
- 向一个域发送请求,如果要请求的域和当前域是不同域,就叫跨域
-
什么是不同域,什么是同域
-
https(协议)://www.imooc.com(域名):443(端口号)/course/list(路径)
-
协议、域名、端口号,任何一个不一样,就是不同域
-
https端口:443 http:80
-
-
跨域请求为什么会被阻止
- 阻止跨域请求,是浏览器本身的一种安全策略--同源策略,
- 其他客户端或者服务器都不存在跨域被阻止的问题
-
跨域解决方案
-
优先使用CORS ,浏览器不支持再使用 JSONP
-
CORS 跨域资源共享
-
是什么
-
主要是后端来解决
-
Access-Control-Allow-Origin: * // 响应头中有这个字段,就表示可以跨域请求,字段的值就是可以跨域访问的域名,*表示全部
-
-
过程
- 浏览器发送请求
- 后端在响应头中添加 Access-Control-Allow-Origin 头信息
- 浏览器接收到响应
- 如果是同域下的请求,浏览器不会额外做什么,这次前后端通信就完成了
- 如果是跨域请求,浏览器会从响应头中查找是否允许跨域访问
- 如果允许跨域,通信圆满完成
- 如果没找到或者不包含想要跨域的域名,就丢弃响应结果
-
兼容性
- IE10 及以上版本浏览器可以使用,老版本浏览器不认识上面那个字段
-
-
JSONP
-
原理
- script 标签跨域不会被浏览器阻止
- JSONP 主要利用 script 标签,加载跨域文件
-
使用
-
服务器端准备好 JSONP 接口
-
手动加载 JSONP 接口,或动态加载接口
-
const script = document.createElement('script'); script.src = 'https://www.xxxx.xxxx/jsonp?callback=handleResponse'; document.body.appendChild(script);
-
-
声明函数
-
const handleResponse = data => { console.log(data); }
-
-
-
-
XHR 对象
-
属性
-
// 注意 send 之前设置,open 之后
-
responseType 和 response 和 responseText
-
// 默认: xhr.responseType = ''; xhr.responseType = 'text'; // 修改值: xhr.responseType = 'json'; // responseText 只能在没有设置 Type 或者设置为 text 时生效,其他时候使用 response // 服务器返回的还是 JSON 格式的(如果使用json),js 帮我们调用了 parse 而已
-
-
timeout
-
设置请求超时时间(单位 ms)
-
xhr.timeout = 10;
-
同名事件处理问题
-
-
withCredentials
-
指定使用 Ajax 发送请求时是否携带 Cookie
-
使用 Ajax 发送请求,默认情况下,同域会携带,跨域不会
-
跨域添加这个设置,就会携带 Cookie
-
当然跨域请求是否成功,还要看服务器设置
-
xhr.withCredentials = true;
-
主要:服务器必须在 Access-Control-Allow-Origin 头信息中设置具体的域名,通配符不行 ,并且配置了 withCredentials,才可以在跨域的时候携带 Cookie
-
-
-
方法
-
abort()
- 中止当前请求,一般配合同名事件使用
- send() 之后调用
- 后面会使用,现在简单了解
-
setRequestHeader()
-
设置请求头信息,参数:(名称, 值)
-
并不是所有的头信息都可以设置,大部分都不行
-
这里学习主要是设置一个头信息:Content-Type
-
// 只有使用 POST 并且需要发送数据时使用,告诉服务器发送的数据是什么格式的 // 格式对应头值: xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('name=alex&age=1'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send( JSON.stringify({ name: 'alex', age: 1 }) );
-
上面两种结果:
- Form Data 和表单发送的一样
- Requset Payload JSON 格式
-
-
-
事件
-
load 事件
-
响应数据可用时触发,可以代替 onreadystatechange 事件
-
xhr.onload = () => { if((xhr.status >= 200) & (xhr.status < 300) || xhr.status === 304){ console.log( xhr.response; ); } }; xhr.addEventListener( 'load', () => { if((xhr.status >= 200) & (xhr.status < 300) || xhr.status === 304){ console.log( xhr.response; ); }, false };
-
-
error 事件
- 请求发生错误时触发,不是响应
-
abort 事件
- 请求中止触发,和abort 方法连用
-
timeout 事件
- 请求超时触发
-
Ajax 进阶
-
FormData
-
FormData 理解为表单提交方式
-
表单的默认提交,会跳转网页,使用 Ajax 不会
-
// 使用 Ajax 提交表单 const login = document.getElementById("login"); // 获取表单 const { username, password } = login; // 获取表单中的input const btn = document.getElementById("submit"); // 因为提交按钮一般没有 name const url = "https://www.imooc.com/api/http/search/suggest?words=js"; // 接口url btn.addEventListener( "click", (e) => { e.preventDefault(); const xhr = new XMLHttpRequest(); xhr.addEventListener( "load", () => { if ( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 ) { console.log(xhr.response); } }, false ); xhr.open("POST", url, true); const data = `username=${username.value}&password=${password.value}`; xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" ); xhr.send(data); }, false );
-
上面非常的麻烦,需要自己组装数据
-
// 使用 FormData 优化 const login = document.getElementById('login'); // 获取表单 const { username, password } = login; // 获取表单中的input const btn = document.getElementById('submit'); // 因为提交按钮一般没有 name const url = "https://www.imooc.com/api/http/search/suggest?words=js"; // 接口url btn.addEventListener( 'click', (e) => { e.preventDefault(); const xhr = new XMLHttpRequest(); xhr.addEventListener( 'load', () => { if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304){; console.log(xhr.response); } }, false ); xhr.open('POST', url, true); const data = new FormData(login); // 直接把表单元素传进去就行了 xhr.send(data); }, false );
-
虽然得到的结果和上面有点不同,但是也是 FormData ,请求头 Content-Type 也有点不一样
-
还可以通过append() 方法添加数据
-
在原基础上添加数据
-
const fd = new FormData(); fd.append('age', 1); fd.append('sex', 'male');
-
-
Ajax 应用
- 封装 Ajax
- 搜索提示
- 二级菜单
- 并行处理
Ajax 扩展
第三方Ajax 库
-
axios
-
基于 Promise 的HTTP 库,可以用在浏览器和node.js 中
-
$ npm install axios <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
-
axios(url, { method: 'post' headers: { 'Content-Type': 'application/json' }, params: { username: 'alex' }, data: { age: 1, sex: 'male' }, // data: 'age=1&sex=male' timeout: 3000, withCredentials: true }).then(response => { console.log(response); }).catch(err => { console.log(err); })
-
axios data 中如果传对象,会自动解析,Content-Type 字段就必须是json 的,此时如果使用 'application/x-www-form-urlencoded' 后端会报错
-
其他方法:
-
axios.get(url, { params: { username: 'alex' } }).then(response => { console.log(response); }).catch(err => { console.log(err); }) // 这里 get 就相当于自己封装的 getjson 了 ,返回的就是 对象 axios.post(url, { username: 'alex' }).then(response => { console.log(response); }).catch(err => { console.log(err); }) // post 方法第二个参数,直接就是数据,如果是字符串,Content-Type 自动帮我们设置成 'application/x-www-form-urlencoded' ,如果使用对象,注意跨域问题 // 其他方法还有 put 和 delete
-
Fetch
-
也是一种前后端的一种方式
-
Fetch 是 Ajax(XMLHttpRequest) 的一种替代方案,也是基于 Promise 的,现在还不太成熟
-
abort timeout 原生还没有,用到需要自己实现
-
const url = "https://www.imooc.com/api/http/search/suggest?words=js"; // 接口url fetch(url) .then((response) => { // console.log(response); if (response.ok) { return response.json(); } else { throw new Error(`HTTP 状态码异常${response.status}`); } }) .then((data) => console.log(data)) .catch((err) => console.log(err)); // body: (…) body: ReadableStream bodyUsed: false headers: Headers {} ok: true redirected: false status: 200 statusText: "OK" type: "cors" url: "https://www.imooc.com/api/http/json/search/suggest?words=js" // body/bodyUsed // body 可读数据流,只能读一次,读过之后就不能再读了 // ok // 如果为 true ,表示可以读取数据,不用再判断 HTTP 状态码 // json 方法输出数据,但是注意,只能读一次,第二次会报错,上锁了 // 第一个参数是url,第二个参数是用来配置 fetch 的,method、但是没有params,有body 请求体携带的数据、headers 头信息 // body 不能直接传对象, 需要自己转 json ,用的时候要是报错就注意一下,同时,使用json 需要和后端沟通 // mode: 'cors' 这个也是默认的值,需要跨域必须传 // credentials: 'include' 这里和 withCredentials 不一样,with 这个是传递布尔值,这里是字符串 // 没有 timeout abort 机制,需要用自己实现
-