1. 请求-响应
2. URL和请求方式
它俩是一次请求,必须的条件。
发送请求的时候,必须 指定url和请求方式。
URL
- ajax.itheima.net ---------- 作用是找服务器(不同的域名对应着不同的服务器)
- /api/news ---------- 不要记,每个项目,地址都不同
请求方式:
- GET --------- 获取数据
- POST --------- 提交数据,一般都是为了新增数据
- PUT --------- 修改数据(整体修改)
- PATCH -------- 修改数据(局部修改)
- DELETE -------- 删除数据
- ...........
3. 如何写Ajax代码
- axios使用语法
- 接口文档
axios({
url: '',
method: '',
params: {}, // 接口文档描述的是 query参数、查询参数,那么就写 params
data: {}, // 接口文档描述的是 body参数、请求体,那么就写data
// https://www.axios-http.cn/docs/req_config 全部的配置,都在这里
}).then(result => {
result.data 表示服务器返回的数据
})
4. 请求体
请求体,不止一种格式,有很多格式。
常见的三种请求体格式:
请求体 | 示例 | |
JSON格式 | { bookname: 'xxx' } --> axios会把对象转成JSON格式 | 非常常用 |
FormData对象 | new FormData() | 上传文件的唯一手段 |
查询字符串 | 'bookname=史记&author=司马迁&publisher=xxx' |
5. FormData对象的用法
// 1. 实例化对象
let fd = new FormData()
// 2. 向对象中添加数据
fd.append('uname', 'laotang')
fd.append('age', 20)
fd.append('avatar', 文件对象) // 如何得到文件对象: 文件选择框.files[0]
// 3. AJAX提交数据
axios({
method: '',
url: '',
data: fd
}).then(result => {
})
6. 原生的Ajax实现
// 1. 实例化对象 XMLHttpRequest
let xhr = new XMLHttpRequest()
// 2. 注册事件,loadend事件,当服务器返回结果之后触发;response属性用于接收响应结果的
xhr.addEventListener('loadend', function () {
// console.log(xhr.response) // 输出的就是服务器返回的结果,格式为JSON格式
console.log(JSON.parse(xhr.response))
})
// 3. 调用open方法,设置请求方式、URL,如果有 query参数,携带query参数
// 3.1 没有query参数
xhr.open('请求方式', 'url')
// 3.2 有query参数
let p = { 参数: 值, 参数: 值 }
let u = new URLSearchParams(p)
xhr.open('请求方式', `url?${u.toString()}`)
// 4. 如果有body参数,也就是请求体,需要设置请求头
// 下面的这一行,表示提交的请求体是JSON格式的;如果提交FormData对象,这行不要写
xhr.setRequestHeader('Content-Type', 'application/json')
// 5. 调用send方法,发送请求;
xhr.send(请求体) // 请求体 有 JSON格式;FormData对象格式
7. 封装自己的axios函数
目的:
- 锻炼编程能力
- 了解真正的axios内部的实现
function myAxios ({ url, method = 'GET', params, data }) {
if (!url) {
throw new Error('url必填')
}
return new Promise((resolve, reject) => {
// 1. 实例化 xhr 对象
let xhr = new XMLHttpRequest()
// 2. 注册 loadend 事件,并用 response接收响应结果
xhr.addEventListener('loadend', function () {
// xhr.status ---> 数字,响应状态码
// console.log(xhr.status)
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(xhr.response)
}
})
// 3. 调用open,设置请求方式、URL
// console.log(params)
if (params) {
let u = new URLSearchParams(params)
url = url + '?' + u.toString()
}
xhr.open(method, url)
// 4. 调用send,发送请求
if (data) {
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(JSON.stringify(data))
} else {
xhr.send()
}
})
}
调用自己封装的函数:
// 测试3:发送请求,带body参数(请求体)
myAxios({
method: 'POST',
url: 'http://hjax.it.net/api/register', // 必填
data: {
username: 'laotang108',
password: '123123'
}
}).then(result => {
console.log('这里是then')
console.log(result)
}).catch(err => {
console.log(err)
})
8. Promise对象
基本语法格式
Promise是“承诺”的意思,实例中,它里面的异步操作就相当于一个承诺,而承诺就会有两种结果,要么完成了承诺的内容,要么失败。
所以,使用Promise,分为两大部分,首先是有一个承诺(异步操作),然后再兑现结果。
第一部分:定义“承诺”
// 实例化一个Promise,需要给它传递一个函数作为参数,而该函数又有两个形参,通常用resolve和reject来表示。该函数里面可以写异步请求的代码
// 换个角度,也可以理解为定下了一个承诺
let p = new Promise((resolve, reject) => {
// 形参resolve,单词意思是 完成
// 形参reject ,单词意思是 失败
// ”承诺” 一般都需要一定的期限才能完成,当然也可能失败
// 比如,我承诺 3 年后赚 500万,然后回来娶你。
// 上述承诺可能成功的完成,也可能失败
// 在编程中,如果承诺成功的完成,则调用 resolve 函数;失败调用 reject 函数
setTimeout(() => {
let money = Math.floor(Math.random() * 1000)
if (money >= 500) {
resolve('赚到钱了,回来娶你')
} else {
reject('赚钱太难,找个好人嫁了吧')
}
}, 3000)
});
第二部分:获取“承诺”的结果:
// 通过调用 Promise对象 的then方法和catch方法,分别获取成功的结果和失败的结果
p.then(res => {
console.log(res) // 赚到钱了,回来娶你
}).catch(err => {
console.log(err) // 赚钱太难,找个好人嫁了吧
})
9. async 和 await 修饰符
async和await是获取 Promise对象的结果的另一个方案,用起来比then更简单。
基本语法如下:
// 1. 声明一个 async 函数
async function abc () {
let 结果 = await Promise对象
}
abc()
- async 只能放到函数前(函数可以是任意的函数)
- async 要放到距离await最近的那个函数前面
- await 只能出现在 async 函数中。
- await 会暂停函数的执行,await下一行代码相当于是微任务。
10. Promise.all()静态方法
语法格式如下:
Promise.all([ Promise对象, Promise对象, Promise对象 ]).then(res => {
})
all是所有的意思,等所有的Promise对象都完成,会触发then,res包含所有的3个结果。
其他静态方法:
Promise.resolve() -- 得到成功状态的promise对象
Promise.reject() -- 得到失败状态的promise对象
Promise.race() -- 和all的语法一样,res 只是最快的那个Promise对象的结果
如何得到Promise对象:
- 自己 new Promise(),将成功的结果传给resolve,将失败的结果传递给reject
- 调用 Promise.all() 等静态方法
- 调用 axios() 可以得到 Promise 对象
如何获取Promise对象的结果:
- 对象.then().catch()
- async 和 await 配置,可以得到结果
11. 错误处理
如果你用 .then 获取结果,则在尾端加一个 .catch 即可收集错误信息。
如果你用 async和await获取结果,则用 try...catch...语句收集错误信息。
// 下面的请求地址,故意写错,调试用的
let p = axios({ url: 'http://ajax.it.net/api/province123' })
// 获取结果,方案一:使用 then
// p.then(result => {
// console.log(result)
// }).catch(err => {
// console.log('这里是catch')
// console.log(err)
// })
// 获取结果,方案二:使用 async和await
; (async function () {
try {
// 把全部的代码,都放到try里面
let result = await p
console.log(result)
} catch (e) {
console.log('进入了catch')
console.log(e)
}
})()
12. 了解then的链式调用
对象.then().then().then().catch() 像这样连续的调用then、catch,就是链式调用。
// 1. 得到Promise对象
let p1 = axios({ url: 'http://ajax.itheima.net/api/province' })
let p2 = axios({ url: 'http://ajax.itheima.net/api/news' })
let p3 = axios({ url: 'http://ajax.itheima.net/api/category/top' })
// 2. 获取结果
p1.then(result => {
console.log(result.data) // 第1个请求的结果
return p2
}).then(result => {
console.log(result.data) // 第2个请求的结果
return p3
}).then(result => {
console.log(result.data) // 第3个请求的结果
}).catch(err => {
console.log(err)
})
13. 宏任务和微任务、事件循环
JavaScript是单线程的,也就是说,同一个时刻,JavaScript只能执行一个任务,其他任务只能等待。
14. JavaScript是单线程的
js是运行于浏览器的脚本语言,因其经常涉及操作dom,如果是多线程的,也就意味着,同一个时刻,能够执行多个任务。
试想,如果一个线程修改dom,另一个线程删除dom,那么浏览器就不知道该先执行哪个操作。
所以js执行的时候会按照一个任务一个任务来执行。
最重要的原因,是JS在设计的年代,计算机的配置非常低下,根本没有考虑多线程的事。
15. 同步任务和异步任务
试想一下,如果js的任务都是同步的,那么遇到定时器、网络请求等这类型需要延时执行的任务会发生什么?
页面可能会瘫痪,需要暂停下来等待这些需要很长时间才能执行完毕的代码
所以,又引入了异步任务。
- 同步任务:同步任务不需要进行等待可立即看到执行结果,比如console
- 异步任务:异步任务需要等待一定的时候才能看到结果,比如 setTimeout、网络请求、DOM事件
16. 事件循环(Event Loop)
事件循环的比较简单,它是一个在 "JavaScript 引擎等待任务","执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的无限循环。
引擎的一般算法:
- 当有任务时:
- 从最先进入的任务开始执行。
- 没有其他任务,休眠直到出现任务,然后转到第 1 步。
17. 任务队列
根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源。
在事件循环中,每进行一次循环的关键步骤如下:
- 在此次循环中选择最先进入队列的任务(oldest task),如果有则执行(一次)
- 检查是否存在 微任务(Microtasks),如果存在则不停地执行,直至清空 微任务队列(Microtasks Queue)
- 更新 render(DOM渲染)
- 以上为一次循环,主线程重复执行上述步骤
在上述循环的基础上需要了解几点:
- JS分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,宿主环境管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
18. 宏任务
(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
任务(代码) | 宏任务 | 环境 |
script | 宏任务 | 浏览器 |
事件 | 宏任务 | 浏览器 |
网络请求(Ajax) | 宏任务 | 浏览器 |
setTimeout() 定时器 | 宏任务 | 浏览器 / Node |
fs.readFile() 读取文件 | 宏任务 | Node |
比如去银行排队办业务,每个人的业务就相当于是一个宏任务;
19. 微任务
微任务(microtask)是宏任务中的一个部分,它的执行时机是在同步代码执行之后,下一个宏任务执行之前。
微任务包含:
Promise.then
await
比如一个人,去银行存钱,存钱之后,又进行了一些了操作,比如买纪念币、买理财产品、办信用卡,这些就叫做微任务。
20. 运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(执行栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务里的同步代码执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
21. 面试题分析
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
resolve(3)
console.log(4)
}).then(res => {
console.log(res) // 3
})
setTimeout(() => {
console.log(5)
}, 100)
async function abc() {
await console.log(6) // 左右结合的代码,右先执行
console.log(7)
}
abc()
console.log(8)
setTimeout(() => {
console.log(9)
}, 10)
console.log(10)
22.看接口文档写代码
第一种情况
接口文档描述:Query参数 或 查询参数
具体到代码:
axios({
method: 'XXX',
url: 'xxxxxxxxxxxxxxxxxxxxx',
params: {
参数: 值,
参数: 值
}
})
第二种情况
接口文档描述:/api/getbooks/:id/:appkey
具体到代码:
把 :xxx
换成具体的值即可。
axios({
method: 'XXX',
url: '/api/getbooks/100/laotang', // 这里的100就是id值,laotang就是appkey的值
})
第三种情况(少)
接口文档描述:
- 请求体 或 body参数
- 并指定 Content-Type: application/x-www-form-urlencoded
具体到代码:
axios({
method: 'XXXX',
url: 'xxxxxxxxxxxxxxxxxxx',
data: '参数=值&参数=值' // data要写查询字符串格式
})
第四种情况(多)
接口文档描述:
- 请求体 或 body参数
- 并指定 Content-Type: application/json
具体到代码:
axios({
method: 'XXXX',
url: 'xxxxxxxxxxxxxxxxxxx',
data: { // data写对象格式,axios内部会自动将对象转成JSON
参数: 值,
参数: 值
}
})
第五种情况
接口文档描述:
- 请求体 或 body参数
- 并指定 Content-Type: multipart/form-data
具体到代码:
axios({
method: 'XXXX',
url: 'xxxxxxxxxxxxxxxxxxx',
data: fd // fd 就是 new FormData() 得来的对象
})