Skip to content

第十六章 任务队列


宏任务与微任务

说一说宏任务和微任务对浏览器的阻塞关系

microtask
	promise.[then/catch/finally]
	MutationObserver
		
macrotask
    setTimeout / setInterval
    MessageChannel I/O
    requestIdleCallback 与macrotask性质相同
    requestAnimationFrame 
        请求动画帧,用于动画。
        替代定时器匹配设备刷新率,解决了定时器做动画时间间隔不稳定-不流畅的问题

事件与事件循环

JavaScript是单线程的,事件循环机制通过任务队列(Task Queue)和微任务队列(Microtask Queue)来处理异步任务 主线程执行完同步任务后,会从任务队列中取出任务执行,微任务优先于宏任务

类型

  • 通用事件循环

    事件循环是一种并发模型,本质是一个死循环
    
    事件可以有很多种,比如文件读写事件、信号事件、超时事件、网络 I/O 事件等
    
    以libuv IO事件件库为例,通过fileno文件描述符来指定资源,对其注册可读和可写事件,通过run执行事件队列
  • 浏览器事件循环

    浏览器->环境->子环境
    
    在浏览器中每打开一个页面,就会分配一个它自己的环境。这样,每个页面都有自己的内存、事件循环、DOM等

Promise

同步与异步?阻塞与非阻塞?

  • 同步与异步,针对的是任务 / 事件,是一种结果;可以类比为子循环,Node中的Tick

  • 阻塞与非阻塞,针对的是事件循环调用栈,是一种过程;可以类比为父循环,Node中的主循环

    同步非阻塞
    	等待结果
    	占用资源
    
    同步阻塞
    	等待结果
    	施放资源
    
    异步阻塞:
    	等待处理 
    	不施放资源
    
    异步非阻塞
    	不等待处理
    	施放资源

Promise为什么可以在状态变化之后再获取结果?

Promise本身是基于状态的订阅通知的模式,本身代表状态,当状态改变,然后通知相应的注册函数执行,本身就是一种串行结构

Promise.all和Promise.race说一下,应用场景有哪些?

Promise.all方法有用过吗?实现一下?参数里有不是promise情况怎么做?那想要返回的结果和入参一一对应该怎么做?

Promise.race

  • 资源竞争 && 超时处理。类似于Array.prototype.some的机制,只要有一个执行成功,状态就改变

Promise.all

  • 处理并行请求。等待所有请求完毕(成功)后执行回调,有一个没成功就会进入catch
js
必要的概念:
	1、同步
    2、异步
不同维度排队顺序:  主线程=>微任务队列=>宏任务队列
相同维度下:按执行速度排列
微任务队列优先级最高,大于宏任务
所以线程执行的时候是先去微任务再去宏任务

new构造函数是同步执行,所以new Promise(resolve=>{})类似的执行,属于主线程同步代码
    setTimeout(() => {
        console.log('2')
    }, 0)
    Promise.resolve().then(() => {
        console.log('1')
    })
    console.log("0")
js
js单线程特点
某一时间只做一个任务

主线程:同一时间只能做一件事,除此之外不能做其它的事情
主线程中的任务做完再去其它队列里抓任务来做
 
干活=>看任务=>

基础


定时器

js
定时器作为宏任务,在执行时,会先将定时器任务进行计时,待主线程运行完毕后,立即执行。
    setTimeout(() => {
        console.log("主任务队列,排列1,运行2");
    }, 2000)
    setTimeout(() => {
        console.log("主任务队列,排列2,运行1");
    }, 1000)
    Promise.resolve().then(() => {
        console.log("微任务队列")
    })
    console.log("主线程任务");

Promise微任务执行逻辑

js
Promise主体是主线程任务,Promise.then是微任务,
    setTimeout(() => {
        console.log("宏任务");
    }, 1000);
    new Promise(resolve => {
        console.log("立即执行,属于主线程任务");
        resolve();
    }).then(resolve => {
        console.log("then属于微任务");
    })

DOM渲染逻辑

js
进行DOM渲染时,默认从上到下执行js代码,
如果将script标签放在头部,且js代码执行时间较长,会给用户造成一种页面有问题的感觉。
实际上执行主线程的时候是这样子的。

所以这就是为什么要将script标签放在body之后的原因。

同步

同步指的是队列顺序下的执行。
同步不等于同时。只是间隔时间比较小,最重要的是同步是有顺序的
    let i = 0; //主线程任务
    setTimeout(() => {
        console.log(++i);
    }, 1000)//宏任务队列1 使用主线程i=0,进行操作后,i=1
    setTimeout(() => {
        console.log(++i);
    }, 1000)//宏任务队列2 使用宏任务队列1 i=1,进行操作后i=2

任务轮询

js
    <div class="demo">1%</div>

    <style>
        .demo {
            display: flex;
            align-items: center;
            justify-content: center;
            border: solid 2px #aaa;
            height: 50px;
            width: 1px;
            background: #a44f;
        }
    </style>

function handle() {
        let i = 0;
        let div = document.querySelector(".demo");
        (function run() {
            if (i < 100) {
                setTimeout(() => {
                    ++i;   //关键在这,即便是执行console.log(++i),i也会在任务队列里进行++i操作
                    div.innerHTML = i + "%";
                    div.style.width = i + "px";
                    run();
                }, 10)
            }
        })()
    }
    handle()

任务拆分

js
机器的任务拆分和人相似。
当面对大量计算时,使用拆分效果会更好。

function question_exposed() {
        let num = 987654321;
        let res = 0;
        for (let i = 0; i <= num; i++) {
            res += num--
        }
        console.log(res)
    }
    question_exposed();
    console.log("this is main")


任务拆分并没有本质上减少任务量,只是对于人来说,我们能够知道进度。不过这一点十分重要
    let num = 98765;
    let res = 0;
    function question_exposed() {
        for (let i = 0; i <= num; i++) {
            if (num <= 0) break;
            res += num--
        }
        if (num > 0) {
            console.log(num)
            setTimeout(question_exposed)
        } else {
            console.log(res)
        }
    }
    question_exposed();
    console.log("this is main")

Promise微任务处理

js
有些数据需要在后台进行计算,而前台专注于任务显示。
在后台计算的数据,我们往往需要拿到数据的结果,进行后续的计算。

    async function get_res() {
        let res = await Promise.resolve().then(() => {
            let count = 0;
            for (let i = 0; i <= num; i++) {
                count += num--;
            }
            return count;
        })
        console.log(res);
    }
    let num = 9890000;
    get_res(num);
    console.log("main")