JavaScript 运行机制详解:深入理解Event Loop

1、为什么JavaScript是单线程的?

JS是单线程的语言,也就是CPU同一时间只能处理一个事务,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。因为JS作为浏览器的脚本主要作用就是与用户交互,以及操作DOM,这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

2、为什么需要异步?

如果JS中不存在异步,JS从上至下执行,如果当前JS代码需要进行大量的计算,由于执行时间过长会阻塞页面,页面出现“假死”状态,会造成很不好的用户体验,所以JS中存在异步执行

3、单线程如何实现异步?

通过event loop(事件循环)实现,从event loop谈JS运行机制:

一、JS将所有任务分为两类:同步任务(synchronous)、异步任务(asynchronous)

同步任务指的是,同步任务添加到主线程上,所有同步任务在主线程上排队执行,形成一个执行栈,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,会将异步任务添加到事件线程中,当满足事件触发条件后,将任务添加到event queue 事件队列中,由于JS是单线程的,只有满足两个条件任务队列中的事件才会被执行:

   1)执行栈中的同步任务执行完毕,即主线程空闲时;此时系统就会去查看任务队列,如果有可运行的异步任务就将任务添加到执行栈中开始执行;

   2)如果任务队列中有多个异步任务待执行,那么这些异步任务也要排队等待被执行,并不是添加到任务队列中的异步任务就会立即执行。

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

二、Event Loop

1)首先判断是同步任务还是异步任务,同步任务进入主线程,异步任务进入事件线程,等待满足触发条件后推入任务队列;

2)一旦主线程的执行栈中的同步任务执行完毕,即执行栈空闲,系统会查看任务队里中是否有待执行的异步任务,如果有就添加到执行栈开始执行;

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

三、定时器

除了放置异步任务的事件,"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做"定时器"(timer)功能,也就是定时执行的代码。定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。​​​​​​​

setTimeout(function () {
    console.log('异步任务');
},3000);

一般说:3秒后会执行setTimeout里的回调函数,这样的说法不严谨,准确的理解为:3秒后,将setTimeout里的函数会推入任务队列中,而添加到任务队列中的任务只有等到主线程执行栈中的同步任务执行完毕,才会被执行,所以只有满足3秒后和主线程空闲时才会执行,如果主线程执行内容很多,执行时间超过3秒,那么这个函数只能等待。

注意:setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数不一定会在setTimeout()指定的时间执行。

猜你喜欢

转载自blog.csdn.net/Rnger/article/details/81908070