javascript的单线程和异步机制

   前言:

    虽然自己学了javascript并且一直在使用,但从来没对他的原理进行了解,就只停留在会用阶段(尴尬)。忽然有一天,组长在群里发让写一篇关于‘javascript的单线程和异步机制’的博客。这下可得学一下了。

  单线程与异步:

    说到单线程和异步,我们首先想到的应该是这两两者本身是相互矛盾的,理论上是不能同时存在的,因为是单线程,所以程序的执行顺序就是从上到下依次执行,同一时间内只能有一段代码被执行。那假如这两者同时存在,单线程的程序执行到了需要异步的操作,就会需要等待,这时程序就会停下,后面的代码就不会执行,就会阻塞程序,这显然是不对的,所以单线程是不可以异步的。但是,我们的浏览器本身就存在大量的异步请求,这就注定我们的javascript即使是单线程,也必须支持异步。那怎么办?单线程压根就不支持异步你还非得要异步,这个时候就要感叹前人的智慧了。

  思路:

 虽然JavaScript是单线程的,可是浏览器内部不是单线程的。我们假设JavaScript只执行自己程序的代码,当遇到异步操作时把他丢给浏览器,由浏览器的线程去执行,自己继续往下执行,这样岂不是美哉。那问题来了,异步之后的程序JavaScript还是需要处理的,那怎么办?没关系,让浏览器执行完异步之后,再把异步执行完的东西在给我们JavaScript,让JavaScript去执行不就好了。好了,思路有了,让我们看看具体是怎么实现的吧。

  开始:

                

 上图是我偷来的(嘘!别说)。上图就是JavaScript的工作原理,我们来解释一下上图的各各东西:

        WebAPIs:浏览器为异步任务单独开辟的线程(服务JavaScript的,处理JavaScript的异步)

        虚线那一块(看图):堆(heap)和栈(stack)共同组成了js主线程(这个就是我们JavaScript的线程)

        callback queue(最下面的那个长方形):任务队列,里面放着各种事件,比如我们点击所触发的事件,浏览器会帮我们以任务的形式,把他放入任务队列中

        event loop(那个转圈圈):任务循环,又叫事件循环。

   好,解释完概念,开始正式说一下整体的工作流程。

 流程:

   当我们的程序运行时,执行我们JavaScript的主线程,堆(heap)和栈(stack)共同组成了JavaScript的主线程,函数的执行就是通过进栈和出栈实现的。比如图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还需要执行上面的那几个函数,所以又把这几个函数推入栈中,等到函数执行完,就让函数出栈。当栈中的函数需要异步的时候,主线程会把需要异步的部分推给WebAPIs(浏览器开辟的线程),由WebAPIs去执行。

    当所有函数都执行完毕后,所有的函数就都被推出了栈。这个时候,程序就会通过event loop(事件循环)去callback queue(任务队列)中寻找下一个任务推入栈中。而WebAPIs(浏览器开辟的线程)执行完主程序推给他的异步之后,将处理后的结果以事件的形式丢到callback queue(任务队列)中,这个事件就是我们写代码的时候的回调函数。而这个时候任务队列里的任务正在往栈(stack)中推,所以异步之后的事件也会被推到栈(stack)中执行,但这个时候他已经不在是异步的了而是同步的,JavaScript的主线程是可以执行的。由此无论是同步还是异步,所有的函数全部执行完毕。(event loop(事件循环)总是会循环的查找任务队列里是否还有任务,有就往栈(stack)中推)

 例子:

下面用一个简单的例子说明一下javascript的单线程和异步机制:

setTimeout(function(){
console.log(1);
},0);
console.log(2);

 

  请问是先打印1还是先打印2?有童鞋说先打印1后打印2,可以试一下,是先打印2。好,为什么?这个定时器里是0秒,那不就是说不用等吗?程序不是应该顺序执行下去的吗?让我们结合上面所学到的进行分析:

  setTimeout的作用是在间隔一定的时间后,将回调函数插入任务队列中,等栈中的同步任务都执行完毕后,再执行。因为栈中的同步任务也会耗时,所以间隔的时间一般会大于等于指定的时间。setTimeout(fn, 0)的意思是,将回调函数fn立刻插入任务队列,等待执行,而不是立即执行。

  上面是摘抄过来的,接下来说一下我的理解。程序执行,先把所有程序推入栈中,js主线程开始执行程序,但程序在执行的一开始就遇到了一个定时器,相当于一个异步,行,js主线程不执行,丢给浏览器的线程,主线程向下执行,打印2。浏览器线程执行完异步setTimeout(fn, 0)(虽然就0秒,但人家也是异步),将回调事件fu()堆到任务队列,因为此时主线程栈中已经没有了需要执行的任务,所以此时会通过循环事件向任务队列中拿任务,此时任务队列中的fn()方法就会被推到js主线程的栈中,主线程开始执行fn(),打印1。所以是先打印2后打印1。

如下图所示:

 

总结:

   javascript的单线程和异步机制,总的来说JavaScript一直是单线程的,并不会去实现异步,浏览器才是实现异步的那个家伙,只是浏览器会通过事件驱动把异步之后的操作通过回调函数的形式丢到任务队列,在由JavaScript的主线程去执行回调函数。由此整个主程序都是同步,但由于浏览器线程的帮助,实现了异步的功能。

补充:

任务队列 分为宏任务和微任务:

宏任务队列:setTimeout ,setInterval ,setImmediate,I/O ,UI rendering (浏览器渲染)

微任务队列:process.nextTick(下一个事件轮询的时间点上执行),Promise,Object.observer , MutationObserver(监视 DOM 变动的接口)

主程序是宏任务,程序执行会在执行完一个宏任务之后执行所有微任务,然后在循环执行下一个宏任务。

更多内容:https://www.cnblogs.com/sunmarvell/p/9564815.html


最后说明一下,以上内容都是我通过看别人的文档,并且根据自己的理解添油加醋之后的成果,本人纯属小白,以上内容如果有哪些地方理解的不对希望大家积极指出,我也好学习改正,谢谢。

  借鉴博客:

      https://zhuanlan.zhihu.com/p/23659122?refer=dreawer

      https://www.cnblogs.com/sxz2008/p/6513619.html

      https://blog.csdn.net/qq_28202849/article/details/81168442(这个特别6)

 

posted @ 2019-05-21 11:00  大正与疯  阅读(903)  评论(0编辑  收藏  举报