在前端的日常工作中,我们可能不会特别关注事件队列这样比较抽象的概念,初次接触事件队列是在面试的时候被问到,你知道的----一脸懵逼,然后就各种网上搜索,以下是我的理解,如果有不正确的地方希望大神们指正:
写这篇博客最主要的是参照了“逆光飞舞”的博客:http://blog.csdn.net/w2765006513/article/details/53743051
第一、首先我们得了解前端工程、js 与 进程、线程的关系
ps:关于进程与线程的概念我也不是很清楚,我了解的是一个进程可以包括多个线程。我们可以看一下阮一峰老师的解释: http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
现在的浏览器为了增强稳定性,使用户一个好的上网体验,每打开一个网页就作为独立进程,当浏览器的某个网页由于某种问题不得不退出的时候,不会影响你同时打开的其他网页,因为其他网页在其他进程的空间里,不在同一空间。否者,如果打开的很多网页都在同一个进程里,一旦某个网页奔溃,那你所有的网页都得关掉,这是很糟糕的体验。
前端工程师都知道js是单线程异步的,而浏览器是多线程的(在当前网页的进程内)。我们可以理解js线程是执行栈(js引擎线程),其他是辅助线程。
第二、浏览器的线程与js引擎线程
浏览器是多线程的:除了js引擎线程,还有ui渲染线程、浏览器事件触发线程、http请求线程、eventloop轮询的处理线程。。。。。。
js在浏览器中的运行是这样的:下载、解析、执行。而js在浏览器中的任务是:执行javascript代码,响应用户输入、点击,处理ajax异步请求数据
单线程的含义是js只能在一个线程上运行,也就是说,js同时只能执行一个js任务,其它的任务则会等待执行;js是单线程的,但是js引擎有多个线程,其它的配合执行线程;多线程之间会共享运行资源,浏览器端的js会操作dom,多个线程必然会带来同步问题,所以js核心选择了单线程来避免处理这个麻烦,js可以操作dom,影响渲染,所以js引擎线程与UI线程是互斥的,这也就解释了js运行时会阻塞页面渲染。
第三、 js任务:同步任务与异步任务
- 同步任务:在主线程排队支持的任务,前一个任务执行完毕后,执行后一个任务,形成一个执行栈,线程执行时在内存形成的空间为栈,进程形成堆结构,这是内存的结构。执行栈可以实现函数的层层调用。注意不要理解成同步代码进入栈中,按栈的出栈顺序来执行。
- 异步任务会被主线程挂起,不会进入主线程,而是进入消息队列,而且必须指定回调函数,只有消息队列通知主线程,并且执行栈为空时,该消息对应的任务才会进入执行栈获得执行的机会。
主线程执行的说明: 【js的运行机制】
1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。
第四、事件、定时器与回调函数
- 消息队列队列(或者叫任务队列)是一个事件的队列,IO响应时,会往队列中添加一个消息,此时说明相关的异步代码到了执行的时机,可以进入主线程的执行栈了。
- 主线程读取消息队列,可以读取到对应的事件。
- 消息队列可以响应IO事件,还有用户产生的事件(比如点击鼠标,页面滚动),只要指定了回调函数,就会进入消息队列,等待EventLoop轮询线程处理,是否可以进入主线程的执行栈。
- 消息和回调函数相互联系的含义:主线程读到消息,就会执行相应的回调函数;进入消息队列的消息,必须对应相应的回调函数,否则这个消息会被丢弃不会进入消息队列。
- 消息队列是一个先进先出的队列结构,这就决定了它的执行顺序,先产生的消息会被主线程先读取,会不会执行则会先检查一下执行时间,因为存在setTimeout等定时函数,这类事件产生的消息进入到消息队列,被执行的时机取决与它在队列中的位置和执行时间有关。【上文中使用setTimeout能够避免阻塞UI线程就是这个原因】。
第五、EventLoop
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
简单说,浏览器的两个线程:一个负责程序本身的运行,称为”主线程”;另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为”Event Loop线程”(可以译为”消息线程”)。
由于js是运行在单线程上的,所有浏览器单独开启一个线程来处理事件消息的轮询,避免阻塞js的执行。
异步代码的执行逻辑:
每当遇到I/O的时候,主线程就让EventLoop线程去通知相应的I/O程序,然后接着往后运行,所以不存在等待时间。等到I/O程序完成操作,EventLoop线程把消息添加到消息队列,主线程就调用事先设定的回调函数,完成整个任务。
- JavaIO中包括了网络IO,我们通常把http请求归类为网络IO.
- js的ajax是new XMLHttpRequest()对象实现的,浏览器会新开一个线程来处理http请求,这就是ajax能够实现局部刷新的同时,还能响应用户交互的原因。
这也说明了在处理IO时,浏览器通常会新开启IO线程,这个属于我的推测,并没有查到对应的资料,因为IO涉及的广泛,这话也没错。