引言
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。 这是因为 JavaScript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM而诞生的。比如我们对某个DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉
为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许JavaScript 脚本创建多个线程。于是,JS 中出现了同步和异步。 同步,前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同 步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。 异步你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。 他们的本质区别:这条流水线上各个流程的执行顺序不同。
同步任务:同步任务都在主线程上执行,形成一个执行栈。
异步任务:JS 的异步是通过回调函数实现的。 一般而言,异步任务有以下类型::
1、普通事件,如 click、resize 等
2、资源加载,如 load、error 等
3、定时器,包括 setInterval、setTimeout 等 异步任务相关添加到任务队列中
4、AJAX
事件循环(event loop)
概念:
JavaScript有一个基于事件循环的并发模型,事件循环负责执行代码,收集和处理事件以及执行队列中的子任务。
原因:
JavaScript是单线程(某一刻只执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型。
执行机制:
JS执行代码的逻辑是顺序读取的,但是能够分辨出同步任务的代码和异步任务的代码。
一般来说,当读取到同步任务代码时候会将代码带进执行栈,直接执行;
当JS读取到异步任务的代码(如setTimeout,setInterval,各种普通事件,资源加载等)时,会将这个任务放进浏览器中处理,浏览器处理完毕后(如setTimeout执行完毕,或完成了点击事件等),会将里面的回调函数放入任务队列中,当所有执行栈中的代码执行完毕时候,再将任务队列中的回调函数放入执行栈中执行。不同的是,JS中的Promise对象本身是同步的任务,但当它调用then和catch方法时,会将方法内的回调函数放入另一个任务队列中,因此这里产生了两个任务队列,我们常常把前者称为宏任务队列,后者称为微任务队列。当所有执行栈中的代码执行完毕后,JS引擎会先将微任务队列的回调函数放入执行栈中执行完毕后再将宏任务队列中的回调函数放入执行栈中执行。
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
resolve(3)
})
p.then(res => {
console.log(res)
})
console.log(4)
如该代码中,打印数字先后顺序是1、4、3、2
JS在执行中,顺序读取,先打印1;之后遇到定时器,将它交给浏览器处理,浏览器在0ms后将回调函数放进了宏任务队列;之后读到Promise,当它调用then函数时候将then里面的回调函数放入微任务队列中;再读取到打印4;执行栈清空,之后JS引擎先将微任务队列的回调放入执行栈中执行了,打印3;最后将宏任务队列中的回调函数放进执行栈执行,打印2。
AJAX
定义:
AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。简单地说,就是使用XMLHTTPRequest对象与服务器通信。它可以使用JSON、XML、HTML、text文本等格式发送和接收数据。AJAX最吸引人的就是它的异步特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据或更新页面
概念:
AJAX 是浏览器与服务器进行数据通信的技术。
基本使用:
//获取button元素
const btn = document.querySelector('button')
const result = document.querySelector('#result')
//绑定事件
btn.addEventListener('click',function(){
//创建对象
const xhr = new XMLHttpRequest()
//初始化 设置请求方法和url xhr.open('请求方法','请求地址')
xhr.open('GET','http://127.0.0.1:8000/server')
//发送
xhr.send()
//事件绑定 处理服务端返回的结果
//on 当什么的时候 readystate是xhr对象中的属性,是状态表示
// 有 0 1 2 3 4 一共5个值
// 0表示未初始化,最开始属性的值就是0,
// 1表示open方法调用完毕
// 2表示send方法调用完毕
// 3表示服务端返回了部分结果
// 4表示服务端返回了所有结果
// change 改变
// 这5个值一共改变4次,改一次触发一次事件
xhr.onreadystatechange = function(){
//判断 服务端是否返回所有结果
if(xhr.readyState === 4){
//判断响应状态码 200 404 403 401 500
//2xx都表示成功
if(xhr.status >= 200 && xhr.status<300){
//处理结果 行 头 空行 体
// 1.响应行
console.log(xhr.status) //状态码
console.log(xhr.statusText)//状态字符串
console.log(xhr.getAllResponseHeaders())//所有响应头
console.log(xhr.response)//响应体
result.innerHTML = xhr.response
}
}
}
})
1.创建XMLHTTPRequest对象并定义xhr作为常量接收
2.初始化:调用open方法设置请求方法和url,其中open方法还能够设置第三个参数,true(异步) 或false(同步),默认为true
3.调用send方法发送
4.监听onreadystatechang事件,执行回调,当readystate为4同时状态码在200到300之间时候对服务端返回的数据(主要是响应体xhr.response)执行要做的事情
其中,初始化中调用open方法设置请求方法主要有GET(获取数据)和POST(提交数据)等
其中若请求方法为GET,可以在url网址中携带查询参数,语法是
http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2
而使用POST方法则可以在send方法中设置参数,格式一般为JSON字符串,作为请求体,提交上去。
回调地狱(Callback Hell)
在AJAX的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到想要的数据,之后才能开始处理数据,这样做看上去并没有什么麻烦,但如果这个时候,我们还需要另外一个AJAX请求,这个新AJAX请求的其中一个参数,得从上一个AJAX请求中获取,这个时候我们就不得不等待上一个接口请求完成之后,再请求后一个接口。
const xhr = new XMLHttpRequest();
xhr.open('get', 'https://xx.com/api?a=2&b=3');
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
//伪代码....
const xhr = new XMLHttpRequest();
xhr.open('get','http://www.xx.com?a'+xhr.responseText);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status<300){
console.log(xhr.responseText)
}
}
}
}
}
}
当出现第三个AJAX(甚至更多)仍然依赖上一个请求时,我们的代码就变成了一场灾难。
这场灾难,往往也被称为回调地狱。
Promise
定义:
Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值。
好处:
1. 逻辑更清晰
2. 了解 axios 函数内部运作机制
3. 能解决回调函数地狱问题
基本使用:
const p = new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest()
xhr.open('GET','http://xx/api/province123')
xhr.send()
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
if(xhr.status>=200&&xhr.status<300){
console.log(xhr.response)
resolve(xhr.response)
}else{
reject(new Error(xhr.response))
}
}
}
}).then(result=>{
document.querySelector('.my-p').innerHTML = JSON.parse(result).list.join('<br>')
}).catch(error=>{
console.dir(error)
document.querySelector('.my-p').innerHTML = error.message
})
1.创建Promise对象,内部传入以resolve函数和reject函数为参数的函数,定义常量p接收
2.内部执行例如AJAX的请求,将成功返回的请求体作为参数传入resovle函数,调用resovle后会调用then函数,此时promise的状态由pending变为fullfilled;将没有成功返回的请求体作为参数传入reject函数,调用reject后则执行catch函数,prmise状态由pending变为rejected;其中最后可以调用finally函数,最后一定调用
以上为Promise的基本用法,此后基于Promise对象,ES7提出了async和await作为解决异步的终极方案。
async和await
异步函数 async function 中可以使用 await 指令,await 指令后必须跟着一个 Promise,异步函数会在这个 Promise 运行中暂停,直到其运行结束再继续运行。
异步函数实际上原理与 Promise 原生 API 的机制是一模一样的,只不过更便于程序员阅读。
基本使用:
// 1. 定义async修饰函数
async function getData() {
// 2. await等待Promise对象成功的结果
const pObj = await axios({url: 'http://xxx/api/province'})
const pname = pObj.data.list[0]
const cObj = await axios({url: 'http://xxx/api/city', params: { pname }})
const cname = cObj.data.list[0]
const aObj = await axios({url: 'http://xxx/api/area', params: { pname, cname }})
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
axios
概念:
Axios 是一个基于Promise 网络请求库,作用于node.js 和浏览器中。 在服务端它使用原生 node.js http
模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
特性:
- 从浏览器创建XMLHttpRequest
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 超时处理
- 查询参数序列化支持嵌套项处理
- 自动将请求体序列化为:
- JSON (
application/json
) - Multipart / FormData (
multipart/form-data
) - URL encoded form (
application/x-www-form-urlencoded
)
- JSON (
- 将 HTML Form 转换成 JSON 进行请求
- 自动转换JSON数据
基本使用:
btn.onclick = ()=>{
axios({
//请求方法
method:'POST',
//url
url:'/axios-server',
//url查询参数
params:{
vip:10,
level:30
},
//头信息
headers:{
a:100,
b:200
},
//请求体参数
data:{
username:'admin',
password:'admin'
}
}).then(response=>{
console.log(response)
//响应状态码
console.log(response.status)
//响应状态字符串
console.log(response.statusText)
//响应头信息
console.log(response.headers)
//响应体
console.log(response.data)
})
}
以对象形式,传入各种配置项的参数,将返回的结果作为then的参数接收并处理。
由于篇幅关系,就只能将基本的AJAX、Promise、axios概念和用法介绍到这,更详细的用法待日后学习。