javascript单线程,异步与执行机制
js的单线程模型与游览器的进程/线程息息相关,在了解js单线程与异步的时候,建议先看看这篇文章
为什么是单线程
- 由于js是可操作dom的,如果js是多线程,在多线程的交互下,处于界面中的dom节点就可能成为一个临界资源。
- 这个时候,如果两个线程同时操作一个dom,一个负责修改,一个负责删除,这时就会出现问题。
- 虽然可以通过锁来解决上面的问题,但为了避免因为引入了锁而带来更大的复杂性,js在最初就选择了单线程。
- 强调一下,这里的单线程指的是渲染主线程,因为js引擎是运行在其上的,js并没有单独的运行线程,下面强调的主线程均为游览器的渲染主线程,可以理解为js运行和页面渲染都是在这一个线程上工作。
为什么需要异步
- 由于js是可操纵dom的,如果在修改这些dom的同时渲染界面(即js引擎和blink引擎同时运行),那么渲染前后获得的元素数据就可能不一致了。
- 为了防止渲染出现不可预期的结果,浏览器将gui渲染与js运行设置为互斥关系,当js引擎执行时,gui渲染就会被挂起,等到js空闲时才会被执行。
- 所以,如果js执行时间过长(同步ajax),就会让页面卡死,造成渲染阻塞。因此,js的异步特性就显得很有必要了。
如何实现异步
- 通过事件驱动机制,来实现异步任务等待,同步任务先执行。
- 当js在主线程上执行完同步任务后,再自动去拿留待的异步任务去执行。
异步编程模型
传统异步回调的问题
- 代码可读性
- 流程控制
- 异常和错误处理
异步编程的变革
- Promise
- Generator
- Async/await
执行机制
- js执行涉及主线程和执行栈,所有的程序任务都会被放到执行栈中被js引擎执行。
- js执行采用后进先出的原则。当函数执行的时候,会被添加到栈的顶部;当执行栈执行完后,就会从栈顶被移出,直到栈内被清空。
- 主线程上的程序执行,由js引擎负责;事件队列,由事件触发线程管理。
事件驱动机制
- 事件驱动机制(event driven)通过事件队列(event queue)和事件循环(event loop)来实现。
- 事件队列(event queue),也称消息队列/任务队列,由异步I/O操作发起,里面存放着各种事件消息,这些消息都关联着回调函数。
- 事件循环(event loop),是指主线程重复从消息队列中取消息、执行的过程,这些消息有些是js产生的,有些是页面加载,更新和网络请求产生的。
- 模型图示

任务类型
- 从执行时机的角度
- 同步任务,存放在执行栈中,会被主线程依次执行的任务
- 异步任务,存放在事件队列中,会在异步操作有了结果后,将注册回调放入这个队列,等待主线程空闲时,被拉取到执行栈中执行。(空闲时,意味着同步任务已被执行完,执行栈为空了)
- 从提供者的角度
- 宏任务(macrotask),由宿主环境提供——全局script,setTimeout,setInterval,setImmediate,I/O,UI rendering,postMessage,MessageChannel
- 微任务(microtask),由语言标准提供——Promise.then,process.nextTick,Object.observe(已废弃),MutationObserver
任务机制
- 所有同步任务在执行完之前,任何的异步任务是不会执行的。
console.log("A");
setTimeout(function(){
console.log("B");
},0);
while(true){}
// 结果为A。因为同步任务被死循环卡住了,任务队列里的任务不会被主线程拉取进执行栈
- 每执行一个宏任务后,就会执行所有微任务。

- 为了使js任务与dom任务能够有序执行,会在一个task执行结束后,在下一个task执行开始前,对页面进行重新渲染 (task(宏->微)->render->task(宏->微)-->...)
宏任务/微任务拓展
- 1个事件循环中,宏任务可以有多个,微任务只有1个。
- 以银行排号为例,1个柜台对应多个用户,每个用户都是1个宏任务,当用户办完(宏)主任务后,突然想到要办理很多(微)次任务,银行柜员会一次帮他解决所有需求,而不是让他重新排队
- 程序模型图示

- 执行机制详述
- 执行一个宏任务,主栈中没有就从事件队列中获取。
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中。
- 宏任务执行完毕后,立即依次执行当前微任务队列中的所有微任务。
- 当微任务执行完毕,开始检查渲染,然后blink引擎接管渲染。
- 渲染完毕后,js引擎继续接管,开始下一个宏任务。
console.log('1');
setTimeout(function() {
console.log('5');
Promise.resolve().then(function() {
console.log('6');
})
}, 0);
Promise.resolve().then(function() {
console.log('3');
}).then(function() {
console.log('4');
});
console.log('2');
// 1,2,3,4,5,6
// 第一轮任务中,宏任务为全局script(刚好处于执行栈内,不用在事件队列中取),所以先是1,2;
// 同时,由于执行过程中遇到了setTimeout,将其再放入宏任务队列,遇到了promise,将其放入微任务队列;
// 该轮宏任务执行完毕后,开始执行微任务,将微任务全部取出,一次执行,因此再是3,4;
// 开始第二轮任务,取出的宏任务为setTimeout回调,因此结果是5;
// 同时执行这轮宏任务回调时,又遇到promise,再将其放入微任务队列;
// 当这轮宏任务setTimeout回调结束后,立即刚才加入的微任务取出执行,因此结果为6;
api优先级顺序
- html5新特性MutationObserver属于微任务,优先级小于Promise
- html5新特性MessageChannel属于宏任务,优先级是:setImmediate->MessageChannel->setTimeout。
- 在node环境的微任务执行中,process.nextTick的优先级高于promise。
- 在node环境的宏任务执行中,setImmediate的优先级高于setTimeout。
Vue.nextTick实现
- 2.4版本时,Vue通过利用MutationObserver来模拟nextTick(MutationObserver为H5新特性,用于监听一个dom变动, 当dom对象树发生任何变动时,Mutation Observer会得到通知)
- 2.5版本开始,nextTick实现移除了MutationObserver的方式(兼容性原因), 取而代之的是使用MessageChannel (当然,默认情况仍然是Promise,不支持才兼容的)
- 由于,js执行是单线程,在一个tick的过程中,可能会存在多次修改数据,vue会把这些数据修改先统一push到一个队列里,然后内部调用1次nextTick去更新视图。
- 因此,vue从数据改变到dom视图变化是需要在下一个tick才能完成的,这种数据驱动变化的原理符合游览器的原理(js运行和gui渲染互斥)和处理策略(task(宏->微)->render->task(宏->微)-->...)
- 最终,Vue.nextTick采取的策略是默认走 microtask,对于一些dom交互事件,如v-on绑定的事件回调函数的处理,会强制走macrotask。对于macrotask的执行,vue优先检测是否支持原生setImmediate(高版本游览器支持),不支持的话再去检测是否支持原生的MessageChannel,如果也不支持的话就会降级为setTimeout 0。
参考
- https://segmentfault.com/a/1190000004322358
- http://www.imweb.io/topic/58e3bfa845e5c13468f567d5
- http://www.cnblogs.com/mininice/p/4298952.html
- https://juejin.im/post/59e85eebf265da430d571f89
- http://www.dailichun.com/2018/01/21/js_singlethread_eventloop.html
- https://juejin.im/post/5a6ad46ef265da3e513352c8
javascript单线程,异步与执行机制的更多相关文章
- 【JS】JavaScript引擎的内部执行机制
近期在复习JavaScript,看到setTimeout函数时.想起曾经刚学时,在一本书上看过setTimeout()里的回调函数执行的间隔时间有昌不是后面设置的值.曾经没想太多.网上看了JS大 ...
- 【THE LAST TIME】彻底吃透 JavaScript 执行机制
前言 The last time, I have learned [THE LAST TIME]一直是我想写的一个系列,旨在厚积薄发,重温前端. 也是给自己的查缺补漏和技术分享. 欢迎大家多多评论指点 ...
- javascript的异步编程
同步与异步 介绍异步之前,回顾一下,所谓同步编程,就是计算机一行一行按顺序依次执行代码,当前代码任务耗时执行会阻塞后续代码的执行. 同步编程,即是一种典型的请求-响应模型,当请求调用一个函数或方法后, ...
- 0182 JavaScript执行机制:单线程,同步任务和异步任务,执行栈,消息队列,事件循环
以下代码执行的结果是什么? [结果是1 2 3 ] console.log(1); setTimeout(function () { console.log(3); }, 1000); console ...
- JavaScript单线程和异步机制
随着对JavaScript学习的深入和实践经验的积累,一些原理和底层的东西也开始逐渐了解.早先也看过一些关于js单线程和事件循环的文章,不过当时看的似懂非懂,只留了一个大概的印象:浏览器中的js程序时 ...
- javaScript的执行机制-同步任务-异步任务-微任务-宏任务
一.概念理解 1.关于javascript javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变.所以一切javascr ...
- 我理解的javascript单线程机制
废话不多说,我们先来看几个例子: 1. setTimeout( console.log(2); result: 2 1 2. console.log(100 setTimeout( cons ...
- JavaScript的异步机制
我们经常说JS是单线程的,比如node.js研讨会上大家都说JS的特色之一是单线程的,这样使JS更简单明了,可是大家真的理解所谓JS的单线程机制吗?单线程时,基于事件的异步机制又该当如何 1 先看下两 ...
- javascript执行机制
文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的 ...
随机推荐
- [python]python的异常处理
异常处理:首先了解异常,程序出现逻辑错误或者用户输入不合法都会引发异常,而这些异常并不是致命的所以不会导致程序崩溃死掉.可以利用Python提供的异常处理机制在异常出现时及时捕获,并从内部自我消化. ...
- 网关鉴权后下游统一filter获取用户信息
1. 场景描述 最近有点忙,在弄微服务nacos+springcloud gateway这块工作,以前只是简单应用,这次因为要对接10几个系统或者平台,还的鉴权,等后续稍微闲点了,把这块东西总结下. ...
- 品Spring:bean工厂后处理器的调用规则
上一篇文章介绍了对@Configuration类的处理逻辑,这些逻辑都写在ConfigurationClassPostProcessor类中. 这个类不仅是一个“bean工厂后处理器”,还是一个“be ...
- 前端使用lodop插件进行打印设置
先前梳理了后台打印导出参考:https://www.cnblogs.com/yyk1226/p/9856032.html,但是没有显示出来打印预览页面. 本章使用Lodop插件进行打印设置,实现打印机 ...
- 常用Form表单正则表达式
前端常用form表单提交,校验正则表达式奉上!/** * 邮箱 * @param {*} s */ export function isEmail (s) { return /^([a-zA-Z0-9 ...
- TCP三次握手和四次握手全过程 为什么要三次握手而不是二次握手?
三次握手 第一次握手: 客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送 ...
- mac下安装jmeter
jmeter官网下载 双击解压 命令行进入/Users/yanguobin/apache-jmeter-5.1.1/bin目录下,输入sh jmeter即可启动 也可以 配置环境变量 vim ~/.b ...
- 分库分表(6)--- SpringBoot+ShardingSphere实现分表+ 读写分离
分库分表(6)--- ShardingSphere实现分表+ 读写分离 有关分库分表前面写了五篇博客: 1.分库分表(1) --- 理论 2.分库分表(2) --- ShardingSphere(理论 ...
- java并发之synchronized详解
前言 多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同 ...
- Map集合(双列集合)
Map集合(双列集合)Map集合是键值对集合. 它的元素是由两个值组成的,元素的格式是:key=value. Map集合形式:{key1=value1 , key2=value2 , key3=val ...