js的单线程和浏览器的多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aliujiujiang/article/details/81154367

js执行是单线程:(发送请求,接受请求,渲染页面,执行js等等这些就是一个个线程。)

JS引擎

通常讲到浏览器的时候,我们会说到两个引擎:渲染引擎和JS引擎。渲染引擎就是如何渲染页面,Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎对同一个样式的实现不一致,就导致了经常被人诟病的浏览器样式兼容性问题。这里我们不做具体讨论。

JS引擎可以说是JS虚拟机,负责JS代码的解析和执行。通常包括以下几个步骤:

  • 词法分析:将源代码分解为有意义的分词
  • 语法分析:用语法分析器将分词解析成语法树
  • 代码生成:生成机器能运行的代码
  • 代码执行

不同浏览器的JS引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。

之所以说JavaScript是单线程,就是因为浏览器在运行时只开启了一个JS引擎线程来解析和执行JS。那为什么只有一个引擎呢?如果同时有两个线程去操作DOM,浏览器是不是又要不知所措了。

所以,虽然JavaScript是单线程的,可是浏览器内部不是单线程的。一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的。

一个浏览器通常由以下几个常驻的线程:

  • 渲染引擎线程:顾名思义,该线程负责页面的渲染
  • JS引擎线程:负责JS的解析和执行
  • 定时触发器线程:处理定时事件,比如setTimeout, setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求

需要注意的是,渲染线程和JS引擎线程是不能同时进行的。渲染线程在执行任务的时候,JS引擎线程会被挂起。因为JS可以操作DOM,若在渲染中JS处理了DOM,浏览器可能就不知所措了。

同步执行和异步执行

  1. 同步:只有前一个任务执行完毕,才能执行后一个任务
  2. 异步:当同步任务执行到某个 WebAPI 时,就会触发异步操作,此时浏览器会单独开线程去处理这些异步任务。

任务队列、回调队列、事件循环

WebAPI 是啥?浏览器事件、定时器、ajax,这些操作不会阻塞 JS 的执行,JS 会跳过当前代码,执行后续代码

  • 任务队列( Task Queue ):主线程执行完毕后所触发的异步任务( WebAPIs ),叫任务队列
  • 回调队列( Callback Queue ):这些异步 WebAPI 执行完成后得到的结果,会添加到 callback queue
  • 事件循环( Event Loop ):只要主线程的同步任务执行完毕,就会不断的读取 "回调队列" 中的回调函数,到主线程中执行,这个过程不断循环往复

如何知道主线程执行执行完毕?JS引擎存在 monitoring process 进程,会持续不断的检查主线程执行为空,一旦为空,就会去 callback queue 中检查是否有等待被调用的函数。

console.log('1');
setTimeout(function() {
    console.log('2');
}, 0);
console.log('3');
  • 打印1
  • 遇到 WebAPI( setTimeout ) ,浏览器新开定时器线程处理,执行完成后把回调函数存放到回调队列中。专业一点的说发: JS 引擎遇到异步任务后不会一直等待其返回结果,而是将这个任务挂起交給其他浏览器线程处理,自己继续执行主线程中的其他任务。这个异步任务执行完毕后,把结果返回给回调队列。被放入的代码不会被立即执行。而是当主线程所有同步任务执行完毕, monitoring process 进程就会把 "回调队列" 中的第一个回调代码放入主线程。然后主线程执行代码。如此反复
  • 打印3 异步 setTimeout 不会阻塞同步代码,因此会首先打印3
  • 主线程执行完毕后,执行 Callback Queue 打印2

异步任务的执行优先级并不相同,它们被分为两类:微任务( micro task ) 和 宏任务( macro task ) 根据异步事件的类型,这些事件实际上会被派发对应的宏任务和微任务中,在当前主线程执行完毕后,

  1. 会先查看微任务中是否有事件存在,如果不存在,则再去找宏任务
  2. 如果存在,则会依次执行队列中的参数,直到微任务列表为空,让后去宏任务中一次读取事件到主线程中执行,如此反复 当前主线程执行完毕后,会首先处理微任务队列中的事件,让后再去读取宏任务队列的事件。在同一次事件循环中,微任务永远在宏任务之前执行。
  1. 宏任务( macro-task ):整体 scriptsetTimeoutsetIntervalUI交互事件I/O
  2. 微任务( micro-task ):process.nextTickPromiseMutaionObserver
(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function (resolve, reject) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()
1. setTimeout:宏任务:存入宏任务队列
2. Promise:函数本身是同步执行的( **Promise** 只有一个参数,默认new的时候就会同步执行), `.then` 是异步,因此依次打印1、2  `.then` 存入微任务中
3. 打印3( 第一次主线程执行完毕 )
4. 执行微任务中的回调函数:5, 让后执行宏任务中的 `setTimeout` 4
// 最终结果1,2,3,5,4

猜你喜欢

转载自blog.csdn.net/aliujiujiang/article/details/81154367