理解JS同步、异步和事件循环

JS之所以存在同步和异步,都源于JS是一门单线程脚本语言,也就是说同一时间只能用来做一件事,至于为什么说是单线程,那是由js这门语言的用途特征所决定的,js用来页面与用户之间的交互,其中包括操作DOM,如果是多线程,这会造成很复杂的同步问题,例如在创建DOM节点的同时删除同一个DOM节点,那这时就出现了矛盾,JS引擎以哪个线程为主呢?所以单线程这个本质特征在目前甚至未来都基本不会改变。

一、主线程和工作线程

JS引擎中负责解析和运行的线程只有一个,我们把它叫做主线程。

除了主线程之外,还有其他线程,例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在Node.js中)等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分。不妨叫它们工作线程

也许会有疑问,既然有工作线程,为何js还说是单线程呢?按我的理解是这样的,其实JS是单线程没错,但是浏览器是多线程,也就是说JS引擎可以通过调用浏览器中的一些线程服务,例如发出请求、计时啊等等都是交给浏览器来完成的,浏览器完成后会通知主线程,然后主线程会执行相应的消息。

二、同步和异步

同步就类似于排队过独木桥,只有前一个人先过,后面的人才能接着过,同理,同步js引擎按照顺序执行完前面的事后才能执行后面的事。下面会在控制台按顺序打印出信息,这就是同步。

console.log('我第一个过桥');
console.log('我第二个过桥');

异步就类似于排队在银行柜台取钱,你突然发现忘记拿银行卡了,那这时肯定得回家拿银行卡才可以拿到钱,但是你要是家离得远的话,不可能后面排队的人都要等你,所以只能先让其他人办理,等你拿卡回来后再接着办理。

console.log('我要拿钱');
setTimeout(function () {
  console.log('我忘记带卡了,晚点再拿钱');
},1000)
console.log('我要拿钱');

同步我们好理解,就是按顺序来嘛,那么我们为什么需要异步呢?

我们前面提到,js是单线程的,也就是一件事执行完才能执行另一件事,但是,如果前一件事的工作量特别大,那就会导致整个流程都停滞下来。如果是因为计算量大导致CPU忙不过来那倒可以原谅,但是很多时候很多CPU闲到发慌而流程却还停滞着,例如IO设备很慢(打印机不太行,打印点东西要一年)或者发送的请求始终得不到响应等情况,那CPU就没事干只能干等着,直到执行结束后才能执行后面的任务。

所以JS设计者就意识到这样不行,不能让CPU这么闲(资产阶级的压迫思想),于是,异步就在这压迫剥削制度下出现了。它的思想就是:既然一时半会干不完,那么到一边慢慢干吧,我先干其他的,你事情做完和我说一下。就是说先不管耗时大的任务,先挂起到等待队列,我先执行其它的。

那么,异步怎么在这混乱时代出现的呢?

  • 主线程发起一个异步请求,相应的工作线程接收请求并告知主线程已收到(异步函数返回);
  • 主线程可以继续执行后面的代码,同时工作线程执行异步任务;
  • 工作线程完成工作后,通知主线程;
  • 主线程收到通知后,执行一定的动作(调用回调函数)。

其实,我们为DOM绑定事件就是一个异步的过程。例如:

扫描二维码关注公众号,回复: 10120404 查看本文章
var button = document.getElement('#btn');
button.addEventListener('click', function(e) {
    console.log('按钮');
});

从事件上来看,我们添加一个监听函数,当按钮被点击时就出发事件,执行回调函数,打印信息。

从异步过程的角度上来看,addEventListener函数就是异步过程的发起函数,或者可以叫做异步任务注册函数。事件监听函数就是异步过程的回调函数,它用来处理事件触发产生的结果。
事件触发时,表示异步任务完成,会将事件监听器函数封装成一条消息放到消息队列中,等待主线程执行。

三、任务队列(消息队列)

"任务队列"是一个事件的队列(也可以理解成消息的队列),工作线程完成一项任务,就在"任务队列"中添加一个事件(也可以理解为发送一条消息),表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。

说到这里,不得不提一下JS的运行机制:

  • 所有同步任务都在主线程上执行,形成一个执行栈
  • 主线程发起异步请求,相应的工作线程就会去执行异步任务,
    主线程可以继续执行后面的代码
  • 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务
    有了运行结果,就在"任务队列"之中放置一个事件,也就是一个消息。
  • 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队
    列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状
    态,进入执行栈,开始执行。
  • 主线程把当前的事件执行完成之后,再去读取任务队列,如此反复重复
    执行,这样就行程了事件循环

也就是,主线程将所有同步任务执行完后,就会去任务队列中取出任务进行执行,这个不断取出任务、执行任务的过程也就是我们经常听到的事件循环,直到任务队列为空。

我们用一张图来理解更直观一点。

我们从左到右看,主线程发起异步任务后就继续执行其它代码,异步任务交给其它工作线程执行,工作线程执行完后向消息队列添加一个消息,主线程再执行完其它代码后,就逐个从消息队列中取出消息并执行。

四、事件循环(Event Loop)

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为事件循环(Event Loop)。

总结,理解JS的运行机制,有利于加深我们对JS使用的理解程度。

反正理解这些,有利于我早日脱离菜鸟行列。

参考来源:https://www.jianshu.com/p/561db8ff3e7a

发布了39 篇原创文章 · 获赞 30 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/HU_YEWEN/article/details/89514993