什么是异步?
我们通过代码来感受一下异步是什么
console.log("start");
setTimeout(function () {
console.log("setTimeout");
}, 1000);
console.log("end");
在不了解异步的情况下,可能会认为打印的顺序就是start,setTimeout,end。但事实上呢?
此时结果并不是我们想的那样,而是按照start,end,setTimeout的顺序打印,仔细想想会发现这种打印方式不会阻塞,如果按照start,setTimeout,end的顺序执行就会在打印完start后等待一秒钟再执行setTimeout,end,在此期间程序就会被阻塞。我们再看一段代码对比一下异步和同步的区别
console.log("100");
alert("200")
console.log("300");
现在的代码就成了你什么时候点击确定按钮,什么时候才会执行下面的代码。此时我们就可以看出同步和异步的区别了:
同步会阻塞线程,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;异步是异步代码会被放入一个任务队列,等到所有其他代码执行后才进行,而不会阻塞线程。
何时需要异步?
在可能发生等待的情况,在等待时我们不能什么都不做,就像alert一样阻塞程序进行,因此,所有的“需要等待的情况”都需要异步。
那什么时候需要等待?
① 定时任务:setTimeout,setInterval
② 网络请求:Ajax请求,动态<img>加载
③ 事件绑定
我们先来看一个Ajax请求的例子
console.log("start");
$.ajax({
type: "GET",
url: 'data.json',
dataType: "json",
success: function (data) {
console.log(data);
}
})
console.log("end");
<img>加载示例
console.log("start");
var img = document.createElement("img");
img.onload = function () {
console.log("loaded")
}
img.src = "01.jpg";
console.log("end");
事件绑定示例
console.log("start");
document.getElementById("btn1").addEventListener("click", function () {
alert("clicked");
})
console.log("end");
为什么会产生异步?
因为js语言的特点是单线程,也就是说,同一个时间只能做一件事,得按顺序一个一个来。我们用之前例子再来看看
console.log("100");
setTimeout(function () {
console.log("200");
});
console.log("300");
分析一下执行步骤:① 执行第一行,打印100;② 执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点:不能同时干两件事);③ 执行最后一行,打印300;④ 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行的;⑤ 发现暂存的setTimeout函数无需等待时间,于是立即执行。
总结一下:
同步和异步的区别:同步会阻塞代码,异步不会;例子就是alert和setTimeout,alert是同步,setTimeout是异步。
前端使用异步的情景:① 定时任务:setTimeout,setInterval;② 网络请求:Ajax请求,动态<img>加载;③ 事件绑定
为什么JS引擎是单线程
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
异步执行
具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个"任务队列"(task queue),只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。