Sky
HOME
HOME
  • 前端笔记

    • CSS

      • 盒子模型
      • 屏幕单位
      • CSS 性能优化
    • JavaScript

      • Methods

        • Scheduler
        • Deep Clone
        • Curry
        • Calculation Function
        • debounce & throttle
      • Function bind
      • Array reduce
      • cross domain
      • event loop
      • ajax
      • event
      • context
      • inheritance
      • prototype
    • HTML

      • html render
      • forbidden <a>
      • <meta>
      • <script>
    • uni-app

      • scroll table

Event Loop

JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事
为了解决单线程运行阻塞问题, JavaScript 用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)

同步&异步任务

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout 定时函数等

同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环

event loop

宏任务&微任务

异步任务还可以细分为微任务与宏任务

    console.log(1)

    setTimeout(()=>{
        console.log(2)
    }, 0)

    new Promise((resolve, reject)=>{
        console.log('new Promise')
        resolve()
    }).then(()=>{
        console.log('then')
    })

    console.log(3)
  • 单纯分同步&异步任务则会输出 -> 1 => 'new Promise' => 3 => 2 => 'then'
  • 但是实际上输出的是 -> 1=>'new Promise'=> 3 => 'then' => 2

原因在于异步任务执行顺序,事件队列其实是一个“先进先出”的数据结构,排在前面的事件会优先被主线程读取
例子中 setTimeout 回调事件是先进入队列中的,按理说应该先于 .then 中的执行,但是结果却偏偏相反
原因在于异步任务还可以细分为微任务与宏任务

宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

  • script整体代码
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI rendering

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

  • Promise.then
  • MutationObserver
  • process.nextTick (Node.js)
  • Object.observe (废弃)

事件循环,宏任务,微任务的关系:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

micro task

async/await

async 是异步的意思,await 则可以理解为等待

async

async 函数是 Generator 函数的语法糖, async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数

    async function fn() {
        return 100;
    }

    fn().then(data => {
        console.log(data); // 100
    });

await

await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值

    async function f(){
        // 等同于
        // return 123
        return await 123
    }
    f().then(v => console.log(v)) // 123

不管 await 后面跟着的是什么,await 都会阻塞后面的代码

    async function fn() {
        console.log(1);
        await fn2(); // 阻塞后面的代码
        console.log(2);
    }
    
    async function fn2() {
        console.log(3);
    }

    fn();
    console.log(4);

上面代码中,await 会阻塞后面的代码(加入微任务队列),等到 fn2&&外面的 console.log(4) 执行完毕,才会回到 fn 执行 console.log(2)

结果为:1 3 4 2

promise 链式分析

    new Promise((resolve) => {
        resolve()
    }).then(() => { // then1
        console.log(1)
        Promise.resolve()
            .then(() => { // then2
                console.log(2)
            })
            .then(() => { // then3
                console.log(3)
                Promise.resolve()
                    .then(() => { // then4
                        console.log(4)
                    }).then(() => { // then5
                        console.log(5)
                    })
            }).then(() => { // then6
                console.log(6)
            })
    }).then(() => { // then7
        console.log(7)
    }).then(() => { // then8
        console.log(8)
        Promise.resolve()
            .then(() => { // then9
                console.log(9)
            }).then(() => { // then10
                console.log(10)
            })
    }).then(() => { // then11
        console.log(11)
    })
执行过程

// 微任务队列 先进先出 入栈: ->[] 出栈: then ->
// 注意点:then中的同步代码执行完毕后 才会继续链式调用,将后面的then继续放入队列中

// -> [then1]
// then1 -> 执行同步代码 打印1 将then2放入队列 then1中的同步代码执行完毕 将then1后的then7放入队列

// -> [then7, then2]
// then2 -> 执行同步代码 打印2 then2中的同步代码执行完毕 将then2后的then3放入队列

// -> [then3, then7]
// then7 -> 执行同步代码 打印7 then7中的同步代码执行完毕 将then8放入队列

// -> [then8, then3]
// then3 -> 执行同步代码 打印3 定义了一个新的promise 将then4放入队列 then3中的同步代码执行完毕 将then3后面的then6放入队列中

// -> [then6, then4, then8]
// then8 -> 执行同步代码 打印8 定义了一个新的promise 将then9放入队列 then8中的同步代码执行完毕 将then8后面的then11放入队列中

// -> [then11, then9, then6, then4]
// then4 -> 执行同步代码 打印4 then4中的同步代码执行完毕 将then5放入队列

// -> [then5, then11, then9, then6]
// then6 -> 执行同步代码 打印6 then6中的同步代码执行完毕

// then9 -> 执行同步代码 打印9 then9中的同步代码执行完毕 将then10放入队列中

// -> [then10, then5, then11]
// -> then11 -> 执行同步代码 打印11 then11中的同步代码执行完毕
// -> then5 -> 执行同步代码 打印5 then5中的同步代码执行完毕
// -> then10 -> 执行同步代码 打印10 then10中的同步代码执行完毕

最近更新: 2023/9/4 17:48
Contributors: liujw155@outlook.com
Prev
cross domain
Next
ajax