📖 前言
之前我们做的页面都是一些没有数据的静态页面(虽然你可能写了一些动画效果,可它却仍然是个静态页面)。那么,啥叫动态页面呢?这就是前端跟网络请求的关系了,如果一个网页在你使用的过程中,向服务器发出了某些网络请求,并接收到了从服务器返回来的数据(当然也可能请求失败),那么,这样的网页才叫做动态页面。也就是说,这里的动态指的是你的浏览器(客户端)和服务器(服务器端)的数据交换。
作为一个真正的前端,所做的事就是负责从客户端获取用户的数据(像是你填写的用户名和密码),然后,把这些数据组织起来,发给服务器端,然后等待服务器端处理完数据(至于服务器接收到数据怎么处理,就是后端的问题了),接收返回的数据,并对这些返回的数据进行不同的处理(比如展示在页面上或者储存起来之类的)。也就是说,写出一个漂亮的页面展示给别人只是前端工作的一部分。
介绍前端网络请求方法之前我们先来学习一些前置知识:
JSON
JavaScript Object Notation(JavaScript 对象表示法)是一种轻量级的数据交换格式。
首先, JSON 的本质是一种数据的格式,取代的是 XML 。那么,这哥俩的作用是啥呢?其实就是一个传输功能,也就是在网络请求的传输过程中,数据的格式必须是 JSON 格式。所以, JSON 不止是前端要用的东西,后端在向前端传输数据的时候,同样也得先把数据格式转化成 JSON 格式,再进行传输。至于为啥要取代以前的 XML 格式,很好理解, JSON 格式简单呗。
下面给一个 json 实例:
{
"data": [
{
"id": 1,
"name": "wjx",
"age": 19
},
{
"id": 2,
"name": "xjw",
"age": 20
},
{
"id": 3,
"name": "jwx",
"age": 21
}
],
"info": "success",
"status": 10000
}
js 中解析 JSON 的方法
JSON.stringify(obj)
js 对象转 json,返回一个 json 格式字符串JSON.parse(json)
json 转 js 对象,返回一个对象
URL
统一资源定位符(Uniform Resource Locator,缩写为 URL),又叫做网页地址,是互联网上标准的资源的地址(Address)。
比如说 www.baidu.com 就是一个简单的 url
URL 组成
-
protocol 协议,常用的协议是
HTTP
、HTTPS
、ftp
-
hostname 主机地址,
域名
或者IP
,比如baidu.com
-
port 端口,http 协议默认端口是 80 端口,https 协议默认 443 端口
-
path 路径,网络资源在服务器中的指定路径
-
query 查询字符串,如果需要从服务器那里查询内容,在这里编辑,多个 query 参数用&符号连接
-
hash 片段,网页中可能会分为不同的片段,如果想访问网页后直接到达指定位置,可以在这部分设置
URL 和 URI
URI, 全称为(Uniform Resource Identifier), 也就是统一资源标识符,它的作用很简单,就是区分互联网上不同的资源。
但是,它并不是我们常说的网址, 网址指的是 URL, 实际上 URI 包含了 URN 和 URL 两个部分,由于 URL 过于普及,就默认将 URI 视为 URL 了。
了解 HTTP
HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。
方法
-
GET:获取资源
-
POST:传输实体主体,主要目的是传输
-
PUT:传输文件,保存到指定的位置
-
HEAD:获得报文首部
-
DELETE:删除文件
-
OPTIONS:查询支持的方法
-
TRACE:追踪路径
-
CONNECT:要求使用隧道协议连接代理
常见状态码
-
2XX 成功
-
200 OK:请求成功
-
204 No Content:请求处理成功,但没有资源可返回
-
-
3XX 重定向
-
301 Moved Permanently:永久重定向
-
302 Found:临时重定向
-
304 Not Modified:服务端资源未改变,可使用缓存(和重定向无关)
-
-
4XX 客户端错误
-
400 Bad Request:请求报文语法错误
-
401 Unauthorized:请求需认证
-
403 Forbidden:不允许访问资源
-
404 Not Found:服务器无请求资源
-
-
5XX 服务器错误
-
500 Internal Server Error:服务器端在执行请求时发生错误
-
503 Service Unavaiable:服务器超负载
-
HTTP 报文
用于 HTTP 协议交互的信息被称为 HTTP 报文,客户端的叫做请求报文,服务端的叫做响应报文。
请求报文和响应报文的结构
结构主要是报文首部、空行(CR+LF)、报文主体。如图所示,清楚明了。
其中请求报文和响应报文是有一定区别的
请求报文的请求行长这样
GET /home HTTP/1.1
包含了请求方法 + URL + HTTP 版本
而响应报文的状态行长这样
HTTP/1.1 200 OK
包含 HTTP 版本 + 状态码 + 原因短语
请求报文和响应报文实例
上面这些都是未经过处理的形式,浏览器会自动帮我们解析为方便查看的格式
我们主要关注的就是请求url
,请求方法
以及状态码
头部字段非常的多,这里不一一列举,只介绍一下头部字段的格式
- 字段名不区分大小写
- 字段名不允许出现空格,不可以出现下划线_
- 字段名后面必须紧接着 :
🛫AJAX
Ajax
全称Asynchronous JavaScript + XML
(用 JavaScript 执行异步网络请求)。它的本质就是用来向服务器端发出网络请求的一种方法。
比如说下面就是一个 ajax 的实例
在Ajax
出现之前,web
程序是这样工作的:
这种交互的的缺陷是显而易见的,任何和服务器的交互都需要刷新页面,用户体验非常差,Ajax
的出现就解决了这个问题。
Ajax
本身不是一种新技术,而是用来描述一种使用现有技术集合实现的一个技术方案,浏览器的XMLHttpRequest
是实现Ajax
最重要的对象(IE6
以下使用ActiveXObject
,别用 IE😥)。
尽管X
在Ajax
中代表XML
, 但由于JSON
的许多优势,比如更加轻量以及作为Javascript
的一部分,目前JSON
的使用比XML
更加普遍。
原生 AJAX 的使用
我们先来对XMLHttpRequest
对象常用的的函数、属性、事件进行分析。
先甩张图出来
函数
open
用于初始化一个请求,用法:
xhr.open(method, url, async)
method
:请求方式,如get、post
url
:请求的url
async
:是否为异步请求 默认为 true
send
用于发送HTTP
请求,即调用该方法后HTTP
请求才会被真正发出,用法:
xhr.send(param)
param
:http 请求的参数,可以为string、Blob
等类型。
abort
用于终止一个 ajax 请求,调用此方法后 readyState 将被设置为 0,用法:
xhr.abort()
setRequestHeader
用于设置HTTP
请求头,此方法必须在open()
方法和send()
之间调用,用法:
xhr.setRequestHeader(header, value)
getResponseHeader
用于获取 http 返回头,如果在返回头中有多个一样的名称,那么返回的值就会是用逗号和空格将值分隔的字符串,用法:
const header = xhr.getResponseHeader(name)
属性
readyState
用来标识当前XMLHttpRequest
对象所处的状态,XMLHttpRequest
对象总是位于下列状态中的一个:
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open() 方法。 |
1 | OPENED | open() 方法已经被调用。 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且头部和状态已经可获得。 |
3 | LOADING | 下载中; responseText 属性已经包含部分数据。 |
4 | DONE | 下载操作已完成。 |
通过判断readyState 我们可以做到一个等待加载的效果。
if (xhr.readyState !== 4) {
// loading...
} else {
// done
}
status
表示http
请求的状态, 初始值为0
。如果服务器没有显式地指定状态码, 那么status
将被设置为默认值, 即200
。
通过判断status我们可以判断请求是否成功,特别的,304
也是成功的返回
response
返回响应的正文,返回的类型由上面的responseType
决定。
withCredentials
ajax
请求默认会携带同源请求的cookie
,而跨域请求则不会携带cookie
,设置xhr
的withCredentials
的属性为true
将允许携带跨域cookie
。
这里提到了跨域,跨域是什么我们等会再讲
事件回调
onreadystatechange
xhr.onreadystatechange = callback
当readyState
属性发生变化时,callback
会被触发。
onloadstart
xhr.onloadstart = callback
在ajax
请求发送之前(readyState==1
后, readyState==2
前),callback
会被触发。
onprogress
xhr.onprogress = function (event) {
console.log(event.loaded / event.total)
}
回调函数可以获取资源总大小total
,已经加载的资源大小loaded
,用这两个值可以计算加载进度。
我们一步一步来看看发送一个 ajax 请求需要做些什么
onload
xhr.onload = callback
当一个资源及其依赖资源已完成加载时,将触发callback
。
onerror
xhr.onerror = callback
当ajax
资源加载失败时会触发callback
。
ontimeout
xhr.ontimeout = callback
当请求响应超时时,会触发callback
,超时时间可使用timeout
属性进行设置。
介绍了XMLHttpRequest
对象的属性和方法之后,我们来看一个完整的 ajax 请求怎么写,
首先,实例化一个 XMLHttpRequest
对象
const xhr = new XMLHttpRequest()
然后再初始化请求 ,设置请求方法,请求 url 和是否为异步请求
xhr.open("get", "http://www.xxx.com/api", true)
设置一下处理响应的回调函数,我们的主要逻辑就在 callback 里面实现
xhr.onreadystatechange = callback
最后发送我们的请求
xhr.send()
这个请求就发出去了
下面给一个完整的示例
//实例化XMLHttpRequest对象
const xhr = new XMLHttpRequest()
//初始化一个get请求
xhr.open("get", "http://musicapi.leanapp.cn/personalized", true)
//接收返回值
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
const res = JSON.parse(xhr.responseText)
console.log(res)
console.log("请求成功")
// do something
} else {
console.log("请求失败")
}
}
}
//发送请求
xhr.send()
如何传参
我们用 GET 请求时,所带数据只能放在 URL 上,按照约定参数的写法(只要后端能够解释出来就行),发给后端
常见:
-
http://api.xxx.com/user?name=wjx&age=19
-
http://api.xxx.com/user/123
我们知道 HTTP 请求方式不止 GET……
我们来看看 POST 请求:
POST 在语义上是传输实体主体,GET 是获取资源
POST 常用与提交表单等
request.open("POST", POSTAPI)
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
// 这样就将数据放在了请求体(request body)中
request.send("name=hzh&age=18&nb=true")
或者其他格式, 类似 multipart/form-data、application/json 等,只要设置好请求头的相应的 Content-Type
其他方法传参方式大同小异
跨域
跨域条件:协议,域名,端口,有一个不同就算跨域。
例如 在https://www.baidu.com请求www.taobao.com就会出现跨域问题
而直接在https://www.taobao.com
不会出现跨域的问题
浏览器的同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。
解决跨域的方法
三种思路
-
绕过浏览器的同源策略
-
使用 Nginx 反向代理
-
Node 中间件代理
-
-
告诉浏览器这个跨域允许
- CORS
-
使用没有跨域限制的方式
-
JSONP
-
postMessage
-
WebSocket
-
JSONP
script
标签的src
属性中的链接可以访问跨域的js
脚本,利用这个特性,服务端不再返回JSON
格式的数据,而是返回一段调用某个函数的js
代码,在src
中进行了调用,这样实现了跨域。
我们来看 一个天气查询查询的 jsonp 接口 https://query.asilu.com/weather/baidu/
它有一个参数是 city
我们先来看看 请求 https://api.asilu.com/weather/?city=${'重庆'}&callback=weather
会返回什么
/** api.asilu.com **/ weather({
city: "重庆",
pm25: "62",
weather: [
{
date: "周三 09月16日",
icon1: "http://s1.bdstatic.com/r/www/aladdin/img/new_weath/bigicon/3",
icon2: "http://s1.bdstatic.com/r/www/aladdin/img/new_weath/bigicon/3",
weather: "阴转小雨",
wind: "北风微风",
temp: "23 ~ 20℃",
},
{
date: "周四",
icon1: "http://s1.bdstatic.com/r/www/aladdin/img/new_weath/icon/8",
icon2: "",
weather: "小雨转阴",
wind: "北风微风",
temp: "22 ~ 19℃",
},
{
date: "周五",
icon1: "http://s1.bdstatic.com/r/www/aladdin/img/new_weath/icon/5",
icon2: "",
weather: "多云转晴",
wind: "东北风微风",
temp: "24 ~ 19℃",
},
{
date: "周六",
icon1: "http://s1.bdstatic.com/r/www/aladdin/img/new_weath/icon/1",
icon2: "",
weather: "晴转多云",
wind: "东北风微风",
temp: "28 ~ 21℃",
},
],
date: "2020-11-24",
s: 1606147200,
})
我们看到响应结果是一段 js 代码 调用了 weather 函数 而给 weather 函数传的数据就是我们需要的数据
那 weather 到底是哪来的?
其实就是我们自己写的函数,比如我的 weather 函数是这么写的,在拿到 data 之后更新 dom
function weather(data) {
const city = document.querySelector(".city")
const weather = document.querySelector(".weather")
city.innerText = data.city
weather.innerText = data.weather[0].weather
}
下一步就是把返回的这段 js 代码插入到我们的页面中了
const script = document.createElement("script")
script.src = `https://api.asilu.com/weather/?city=${"重庆"}&callback=weather`
document.body.appendChild(script)
最终效果
CORS
实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin
就可以开启 CORS。该属性表示哪些域名可以访问资源,如果设置通配符 *
则表示所有网站都可以访问资源。
Node.js 写的后端实现跨域:
前端本地服务 3000 端口跨到后端 8080 端口
const http = require("http")
const cors = res => {
// 根据需要设置
res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8080")
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE")
res.setHeader("Access-Control-Allow-Headers", "X-TOKEN")
res.setHeader("Access-Control-Allow-Credentials", true)
}
const server = http.createServer((request, response) => {
cors(response) // 从这里也可以看出是对响应的处理,加几个响应头告诉浏览器这个响应不要限制 🚫
response.writeHead(200, { "Content-Type": "text/plain" })
response.end("ok")
})
server.listen(8081, () => {
console.log("Server is running at http://localhost:8081")
})
JQuery 中的 AJAX
JQuery 是什么?
jQuery 是一套跨浏览器的 JavaScript 库,简化 HTML 与 JavaScript 之间的操作
大家用原生 JS 操作 DOM 写作业是不是感觉很麻烦,被 DOM 恶心到了(如果现在没有,不久后就有了 😄),JQuery 就是为了让开发者不被 DOM 恶心到(方便操作)而设计出来的库
我们看看用 JQuery 封装好的 ajax 函数怎么调接口:
$("button").click(() => {
$.ajax({
type: "POST",
url: "http://api.example.com/post",
dataType: 'json', // 设置返回值类型
contentType: 'application/json', // 设置参数类型
headers: {'Content-Type','application/json'},// 设置请求头
xhrFields: { withCredentials: true }, // 跨域携带cookie
data: JSON.stringify({a: [{b:1, a:1}]}), // 传递参数
error:function(xhr,status){ // 错误处理
console.log(xhr,status);
},
success: function (data,status) { // 获取结果
console.log(data,status);
}
})
})
可以看到相比于原生 open\send...
会方便很多,这是因为 JQuery 已经为我们封装好了,我们只需要传入相应的参数(配置)即可
JQuery 中的 AJAX 这么简单,所以我们这节课作业就是封装类似的函数.
说下 fetch 和 axios
fetch
fetch API 是一个用用于访问和操纵 HTTP 管道的强大的原生 API,是一个非常底层的 API
这种功能以前是使用 XMLHttpRequest 实现的。Fetch 提供了一个更好的替代方法,可以很容易地被其他技术使用,例如 Service Workers。Fetch 还提供了单个逻辑位置来定义其他 HTTP 相关概念,例如 CORS 和 HTTP 的扩展。
fetch 相比于传统的 XMLHttpRequest 来说有许多好处,我们先用 fetch 来实现一下最开始的用 XMLHttpRequest 实现 ajax 的例子
fetch("http://musicapi.leanapp.cn/personalized")
.then(res => res.json())
.then(console.log)
.catch(console.error)
fetch()请求返回的 res 是 Stream 对象,因此我们调用 res.json 时由于异步读取流对象所以返回的是一个 Promise 对象
利用 ES2016 的 async/await 还可以更优雅:
async function getData(url) {
try {
const res = await fetch(url)
const json = await res.json()
console.log(json)
} catch (error) {
console.log(error)
}
}
当我们用 fetch 发送 post 请求时,需要手动设置 method 参数和 body 参数,如下:
fetch(url, {
method: "post",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: "foo=bar&lorem=ipsum",
})
当然 fetch 也有很多坑,比如不能正确的捕获错误,不能直接传递 js 对象作为参数,另外,fetch 还不支持超时控制等,你们遇到的时候再去了解吧
axios
一个非常优秀的 XMLHttpRequest 请求封装库,比 JQuery.ajax 还优秀
他有很多的优点比如:
- 可以在 node.js 中使用(当然 node 中也有 node-fetch)
- 提供了并发请求的接口
- 支持 Promise API
使用也很简单,很 fetch 类似
axios
.get(url)
.then(res => console.log(res.data))
.catch(err => console.log(err))
附带一个自己对 axios 的封装(其实 axios 已经很方便了不需要特别封装),方便管理 api 和配置