前言
JS是一门单线程语言,那为何能够实现异步操作呢?
单线程和异步操作确实不能同时成为一个语言的特性。JS本身不能实现异步,但是JS的宿主环境(浏览器,Node)是多线程的,宿主环境通过某种方式,使得JS具备了异步的特性。
浏览器
JS是单线程语言,浏览器只分配给JS一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。
浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
任务队列
浏览器为这些异步任务单独开了一个线程,那么主线程是如何知道异步任务是否已经完成呢?这就需要依赖回调函数了,整个程序是靠事件驱动的,每个事件都有相应的回调函数。
setTimeout(function(){
console.log('time up);
},50)
主线程
JS一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行。
- 浏览器为异步任务开启的线程序=>WebAPIs
- 任务队列=>callback queue
- 主线程
- 堆和栈
- 函数的执行就是通过进栈和出栈实现
- 栈stack清空时,说明一个任务已经执行完成,这时会从
callback queue
中寻找下一个任务推入栈中
容易困惑的问题
setTimeout(f1,0)
是否立即执行
setTimeout(()=>{
console.log(1)
},0)
console.log(2)
// output => 2,1
当执行setTimeout后,浏览器会立即把匿名函数放入callback queue
中,等待主线程的召唤,因为此时的stack还有console.log(2)
尚未执行。等console.log(2)
执行完毕后,才通过event loop
把匿名函数放到stack中执行。
-
结论:
setTimeout(f1,0)
并非无意义,若f1是比较耗时的任务,又不在WebAPIs中,那就可以把它放到callback queue
,等待主线程执行完毕后再执行。- 若当前代码执行时间很长,没有办法保证回调函数一定会在setTimeout指定的时间内完成。
-
补充说明:
- HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。
- DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用
requestAnimationFrame()
的效果要好于setTimeout()
浏览器异步机制的使用
- 当我们自己需要写的程序中也有比较耗时的函数。可以通过浏览器提供给我们的浏览器定时事件和事件触发线程。
异步的好处和使用场景
-
同步
-
异步
-
使用场景
程序需要大量I/O操作和用户请求时