js 线程、宏观任务、微观任务、运行机制梳理

4,027 阅读4分钟

简介

为什么要弄清楚线程和任务(宏、微)。我们经常会被问到一个问题,就是当settimeout和http请求回调还有promise的执行顺序。这里就涉及到了这些知识点。以及为什么js过大影响dom渲染。

本文适合有一定经验的前端人员,对js运行机制有一定了解。

1、浏览器进程

首先我们要知道浏览器在渲染一个页面的过程种涉及到哪些工作原理。浏览器是多进程工作原理,什么是进程请自行学习(进程是内存分配的最小单位),一个进程又可以有多个线程(线程是执行的最小单位)。每打开一个tab标签就是新启一个进程(内存是独立的)。

2、浏览器内核

知道了浏览器进程,那么说一下一个进程的渲染过程:

  • GUI渲染线程(渲染Dom页面的)
  • JS引擎线程(运行js的)
  • 事件触发线程(js内的事件)
  • 定时触发器线程
  • 异步http请求线程

注意:这些都是属于浏览器的与js引擎单线程一样是属于浏览器的

GUI渲染线程与JS引擎线程是互斥的。所以会JS如果执行时间过长就会阻塞页面。

那么我们就可以理解了,我们经常遇到的问题,就是当js执行的时候遇到时间定时器、异步加载等,他们的输出结果会在页面所有js执行之后执行的问题了,就是当遇到到时间定时器、异步加载等浏览器的事件触发线程就会触发他会产生一个任务添加到js的任务对列内,同理异步http请求线程、事件触发线程也是一样的。

由进程产生的非js引擎的任务会分成宏、微两种任务对列。

3、宏、微两种任务对列运行顺序

宏任务包括有:setTimeOut、setInterval、setImmediate、I/O、用户交互操作,UI渲染

微任务包括有:Promise(重点)、process.nextTick(nodejs)、Object.observe(不推荐使用)

运行顺序是当js单线程执行完成之后就会去执行任务对列的内容。当有宏微都有的时候执行是,先执行全部的微观任务在执行宏观任务对列内的第一个任务(如果宏任务内还有微任务和宏任务,执行的顺序是一样的),在去执行全部微观任务, 在执行宏观任务对列内的第一个任务。所以下面的代码输出结果是这个样子的:

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

    const intervalId = setInterval(function() {
        console.log(3);
    }, 0);

    setTimeout(function() {
        console.log(10);

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

        new Promise(function(resolve) {
            console.log(11);
            resolve();
        }).then(function() {
            console.log(12);
        }).then(function() {
            console.log(13);
            clearInterval(intervalId);
        })
    }, 0);

    Promise.resolve().then(function() {
        console.log(7);
    }).then(function() {
        console.log(8);
    });
    
    Promise.resolve().then(function() {
        console.log(15);
    }).then(function() {
        console.log(16);
    });
    
    console.log(9);

结果: 1,9,7,15,8,16,2,3,10,11,12,13,14

这里注意: promise的then执行,(7,15,8,16)这个是因为then是异步的

4、Web Worker解决JS如果执行时间过长就会阻塞页面问题

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。具体怎么使用请自行学习。

Web Worker 有以下几个使用注意点:

  • 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
  • DOM 限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和lWorker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。ocation对象。
  • 通信联系:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。使用worker.postMessage()通信。
  • 脚本限制: Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
  • 文件限制:Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

结尾

本文是是自己学习笔记,如有看不懂的可参考以下链接。

参考文章链接如下:

1、浏览器以及js运行原理:segmentfault.com/a/119000001…

2、宏观任务和微观任务原理:juejin.im/post/684490…

3、Web Worker使用:www.ruanyifeng.com/blog/2018/0…