一. 单线程与非阻塞
JavaScript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。
单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。 这是必要的,因为如果 JS 是多线程的,那么当两个线程同时对同一个 DOM 节点进行操作,一个添加,一个删除,那么就会出错,为了避免出现此类错误,就只能选择单线程模式!!!
单线程模式,大大降低了 JS 的执行效率。虽然,Web Worker 出现了,但是该技术实现的多线程有着诸多限制:
- 所有新线程都受主线程的完全控制,不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。
- 这些子线程并没有执行I/O操作的权限,只能为主线程分担一些诸如计算等任务。
非阻塞指的是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
二. 异步与同步
<1> 同步
同步指的是,如果一个函数,在其执行完毕返回时,调用者就可以拿到预期的返回值or看到预期的效果,那么这个函数就是同步的。
function A(){
console.log(2);
}
Math.sqrt(2);
如上,第一个函数,在函数执行完毕返回时,就可以看到打印 2 ;第二个函数,执行完毕返回时,就可以得到执行结果 2的平凡根 。这样的函数都是同步的!!
<2> 异步
异步,与同步相对应,如果一个函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。
fs.readFile('foo.txt', 'utf8', function(err, data) {
console.log(data);
});
如上,我们希望通过fs.readFile函数读取文件foo.txt中的内容,并打印出来。但是在fs.readFile函数执行完毕返回时,我们期望的结果并不会发生,而是要等到文件全部读取完成之后,才会调用传入的回调函数参数,进行打印。这样的函数fs.readFile就是异步的!!!【如果文件很大的话可能要很长时间】。
(1) 异步的执行过程
- JS 引擎遇到一个异步事件,主线程会把该注册函数(事件挂起) ,并通知工作线程来执行
- 相应的工作线程接收请求并告知主线程已收到(异步函数返回);
- 主线程可以继续执行后面的代码,
- 同时工作线程执行异步任务;
- 工作线程完成工作后,JS 会把该事件放进其对应的事件队列中;
- 事件队列中的事件并不会被立即执行, 而是当主线程空闲时,会依照事件队列的优先级,从某一队列中选取排在第一位的事件(回调函数),并把其放入执行栈中执行!!
其中包括两个要素:发起(注册)函数 与 回调函数
setTimeout(fn, 1000);
其中的setTimeout就是异步过程的发起函数,fn是回调函数。
三. 事件循环与事件队列
<1> 事件循环
如上图,JS 引擎的执行机制就是:【同步任务-->微任务-->宏任务】
- 1 先执行所有同步任务,碰到异步任务放到任务队列中
- 2 同步任务执行完毕,开始执行当前所有的异步任务
- 3 先执行任务队列里面所有的微任务
- 4 然后执行一个宏任务
- 5 然后再执行所有的微任务
- 6 再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。
而 3 ~ 6 这个过程,就是
事件循环(Event Loop) 【主线程空闲时,会不断从事件队列中取事件执行!!】从某个事件队列中取一个事件并执行的过程叫做一次循环。
<2> 微任务与宏任务
微任务(JS引擎发起的)总是先于其他任务执行!!!!
<3> 事件队列
需要不同队列!!!
每个任务都有一个任务类型 , 同一个类型的任务必须在一个队列也就是一共有多个队列 , 不同类型的任务可以分属不同的队列,在一个次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行
微队列一直存在,且微队列中的任务永远先于其他任务执行!!!
浏览器必须准备好一个微队列 , 微队列中的任务优先所有其他任务执行他里面的东西 所有都要给我等 连绘制任务 都要等 就是最高优先级了
常见事件队列!!!
- 微队列 : 用户存放需要最快执行的任务 优先级最高
- 交互列队 : 用于存放用户操作后产生的事件处理任务 , 优先级高
- 延时队列 : 用于存放计时器到达后的回调任务 , 优先级中【setTimeOut()...】