从今天开始研究一下javascript的异步相关内容,感兴趣的请关注
我们知道JavaScript的单线程的,这与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准? 所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。 这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。 ajax的同步请求就会导致浏览器产生假死,因为它会锁定浏览器的UI(按钮,菜单,滚动条等),并阻塞所有用户的交互,jquery中的ajax有这样一个同步请求的功能,一定要慎用,尤其是在请求的数据量很大的时候,要避免使用同步请求。 举几个栗子?感受一下异步 后台接口使用easy-mock,官方地址:https://easy-mock.com/ ajax使用axios,基本代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>javascript异步</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<button>点击</button>
<script>
{
let myData = null
//ajax请求
function ajax() {
axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock')
.then(data => {
console.log("ajax返回成功");// handle success
myData = data.data
console.log(myData);
})
.catch(error => {
// console.log(error); // handle error
console.log("ajax返回失败");
})
}
}
</script>
</body>
</html>
我们通过添加一些js来看下效果,
console.log(myData);
setTimeout(() => {
console.log('定时器');
}, 2000);
console.log(myData);
输出,应该没什么悬念
//null
//null
//定时器
执行顺序: 先执行第一个 console.log(myData); 然后遇到了定时器,将定时器挂起(就是暂停了这个定时器) 继续执行第二个 console.log(myData); 没有可以执行的js代码,回头把挂起的任务继续执行下去 继续看下一个栗子
console.log(myData);
ajax()
console.log(myData);
看下输出,依然没有悬念
//null
//null
//ajax返回成功
//{success: true, data: {…}}(这是接口返回的数据,我们不必关心返回的具体内容,只要知道返回了就好,陌上寒注)
执行顺序和上面的定时器基本类似,不在此赘述。 将两个栗子合并,我们看下
console.log(myData);
ajax()
setTimeout(() => {
console.log('定时器');
}, 2000);
console.log(myData);
输出,
//null
//null
//ajax返回成功
//{success: true, data: {…}}
//定时器
发现问题了吗?两个异步函数相遇了,先执行谁?谁跑的快就先执行谁? 也可以这么说,其实这引发了另外一个知识点,
两个 console.log(myData);是同步执行的,他们都在js的主线程上执行, 在主线程之外还存在一个任务队列,任务队列中存放着需要异步执行的内容 当主线程运行完毕之后,就会去执行任务队列中的任务(不断的重复扫描)直到任务队列清空
观察这段代码
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
console.log(3);
输出:1,3,2,这没什么可解释的 再看一段代码
setTimeout(function(){console.log(1);}, 0);
console.log(2);
输出:2,1,为什么会这样? console.log(2);在主线程中,先执行, setTimeout(function(){console.log(1);}, 0);放在了任务队列中, 只有在主线程执行完了才会去执行任务列队中的内容
看这段代码,有助于你的理解
console.log(myData);
ajax()
setTimeout(() => {
console.log('定时器');
}, 2000);
console.log(myData);
const btn = document.querySelector('button')
btn.onclick = () => {
console.log("点击了");
}
我们为button按钮添加了点击事件,在浏览器刷新的同时不停地对按钮进行点击操作(当然是手动点击) 看下输出:
//null
//null
//(10次输出)点击了
//ajax返回成功
//{success: true, data: {…}}
//定时器
//点击了
这样是不是可以理解为什么主线程要去循环扫描任务列队了? 事件循环的每一轮称为一个tick(有没有联想到vue中的nextTick?) 当产生用户交互(鼠标点击事件,页面滚动事件,窗口大小变化事件等等),ajax,定时器,计时器等,会向事件循环中的任务队列添加事件,然后等待执行,
一句话总结就是需要等待但是又不能阻塞程序的时候需要使用异步
千万不要把异步和并行搞混了, 异步是单线程的,并行是多线程的 异步:主线程的任务以同步的方式执行完毕,才会去依次执行任务列队中的异步任务 并行:两个或多个事件链随时间发展交替执行,以至于从更高的层次来看,就像是同时在运行(尽管在任意时刻只处理一个事件)