尝试用几句话讲明白事件循环。附加非常多道 log 顺序题

事件循环

循环,指的就是不断地查看 js 执行栈是否为空。

js 执行栈为空,意味着旧循环的结束,新一轮循环的开始。

一轮事件循环是这样的:

  1. js 执行栈为空
  2. 会先将微任务队列中的所有微任务填入 js 执行栈中。(在这个过程中如果有新的微任务,那么也会不断的放入 js 执行栈中执行。)
  3. 微任务队列为空
  4. 只将宏任务中的一个任务放入 js 执行栈。
  5. js 执行栈执行完宏任务后,再次为空
  6. 重复步骤一。

宏任务和微任务有哪些

宏任务

  • setTimeout
  • setInterval
  • setImmediate (Node 独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

微任务

  • Promises 返回的 then
  • process.nextTick (Node 独有)
  • Object.observe
  • MutationObserver
  • queueMicrotask

宏任务和微任务的区别只需要记住:执行一个宏任务前,要求微任务队列为空。

实战练习:log 输出顺序

题目内容直接从网络随便找的:22道js输出顺序问题,你能做出几道

方法:用草稿纸记录 js 栈、微任务队列和宏任务队列的内容变化。遇到嵌套时,将前提内的所有输出写到一块,等当前一轮循环结束后再处理。

下面案例中,最终输出的数字都是连续的,毕竟这是学习,不是面试。

小试牛刀:

console.log(1)
setTimeout(()=>{
    console.log(5)
},0)
process.nextTick(()=>{
    console.log(3)
})
new Promise((resolve)=>{
    console.log(2)
    resolve()
}).then(()=>{
    console.log(4)
})

开始嵌套:

console.log('1')
setTimeout(() => {
    console.log('6')
}, 0)
process.nextTick(() => {
    console.log('4')
})
new Promise((resolve) => {
    console.log('2')
    resolve()
}).then(() => {
    console.log('5')
})
setTimeout(() => {
    console.log('7')
}, 0)
new Promise((resolve) => {
    console.log('3')
    // 虽然这个 setTimeout 写在这里面
    // 但它本质上和外面的 setTimeout 没区别
    setTimeout(() => {
        console.log('8')
        // 该 setTimeout 的作用仅仅只是将 then
        // 的回调延迟了一轮循环罢了
        resolve()
    }, 0)
}).then(() => {
    console.log('9')
    setTimeout(() => {
        console.log('10')
        new Promise((resolve) => {
            console.log('11')
            resolve()
        }).then(() => {
            console.log('12')
        })
    }, 0)
})

再次嵌套:

console.log(1)
setTimeout(function () {
    console.log(5)
    process.nextTick(function () {
        console.log(7)
    })
    new Promise(function (resolve) {
        console.log(6)
        resolve()
    }).then(function () {
        console.log(8)
    })
})

process.nextTick(function () {
    console.log(3)
})

new Promise(function (resolve) {
    console.log(2)
    resolve()
}).then(function () {
    console.log(4)
})

setTimeout(function () {
    console.log(9)
    process.nextTick(function () {
        console.log(11)
    })
    new Promise(function (resolve) {
        console.log(10)
        resolve()
    }).then(function () {
        console.log(12)
    })
})

如果上面三个你都对了,那么后面的就很简单了:

setTimeout(() => {
    console.log(2)
    Promise.resolve().then(data => {
        console.log(3)
    })
}, 0)
setTimeout(() => {
    console.log(4)
}, 0)
Promise.resolve().then(data => {
    console.log(1)
})

前面的宏任务定时器都是直接就添加进宏任务队列。那么如果有延迟呢?简单,草稿纸上画一个新的区域,
用于计时就可以了!

console.log(1)

setTimeout(function () {
    console.log(6)
}, 0)

setTimeout(function () {
    console.log(7)
    setTimeout(function () {
        console.log(9)
    })
    Promise.resolve().then(function () {
        console.log(8)
    })
}, 200)

Promise.resolve().then(function () {
    console.log(3)
}).then(function () {
    console.log(5)
})
Promise.resolve().then(function () {
    console.log(4)
})
console.log(2)

简单:

console.log(1)

setTimeout(function cb1() {
    console.log(5)
}, 0)

new Promise(function (resolve, reject) {
    console.log(2)
    resolve()
}).then(function cb2() {
    console.log(4)
})

console.log(3)

还是简单

console.log(1)

setTimeout(() => {
    console.log(3)
    new Promise(resolve => {
        resolve()
    }).then(() => {
        console.log(4)
    })
}, 0)

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

console.log(2)

看样子难道是没有更难的题了?

console.log(1)

setTimeout(() => {
    console.log(3)
    new Promise(resolve => {
        console.log(4)
        resolve()
    }).then(() => {
        console.log(5)
    })
}, 0)

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

console.log(2)

现在我已经无需要草稿纸也能写出来了!

console.log(1)
setTimeout(function () {
    console.log(5)
})
Promise.resolve().then(function () {
    console.log(4)
})
console.log(2)
setTimeout(function () {
    console.log(6)
    Promise.resolve().then(function () {
        console.log(7)
    })
}, 500)

setTimeout(function () {
    console.log(8)
    setTimeout(function () {
        console.log(10)
        Promise.resolve().then(function () {
            console.log(11)
        })
    }, 500)
    Promise.resolve().then(function () {
        console.log(9)
    })
}, 600)
console.log(3)

还是简单

function test() {
    console.log('1')
    setTimeout(function () {
        console.log('7')
    }, 1000)
}

test()

setTimeout(function () {
    console.log('4')
})

new Promise(function (resolve) {
    console.log('2')
    setTimeout(function () {
        console.log('6')
    }, 100)
    resolve()
}).then(function () {
    setTimeout(function () {
        console.log('5')
    }, 0)
    console.log('3')
})

console.log('3')

简单,只是把数字换成变量罢了

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

new Promise((resolve, reject) => {
    console.log(1)
    resolve(3)
}).then(val => {
    console.log(val)
})

console.log(2)

加了 for 循环罢了, 有啥区别吗?还是简单!

setTimeout(() => {
    console.log(9)
}, 0)
for (let i = 1; i <= 3; i++) {
    console.log(i)
}
console.log(4)
setTimeout(() => {
    console.log(10)
}, 0)
for (let i = 5; i <= 7; i++) {
    console.log(i)
}
console.log(8)

终于错了一次:重点在于 2 的输出为什么会那么早。原因是 async 函数的异步,指的是返回值是异步获取的。这一点类似于 Promise() 中的回调函数类似。

console.log(1)

async function async1() {
    await async2()
    console.log(5)
}
async function async2() {
    console.log(2)
}
async1()

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

new Promise(resolve => {
    console.log(3)
    resolve()
})
    .then(() => {
        console.log(6)
    })
    .then(() => {
        console.log(7)
    })
console.log(4)

如果将上面代码中的 await 删除,那么 async 关键字的作用就仅仅只有迷惑作用了,完全可以将其删除:

console.log(1)

async function async1() {
    async2()
    console.log(3)
}
async function async2() {
    console.log(2)
}
async1()

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

new Promise(resolve => {
    console.log(4)
    resolve()
})
    .then(() => {
        console.log(6)
    })
    .then(() => {
        console.log(7)
    })
console.log(5)

简单

console.log(1)

function a() {
    return new Promise(resolve => {
        console.log(2)
        setTimeout(() => {
            console.log(4)
        }, 0)
        resolve()
    })
}

a().then(() => {
    console.log(3)
})

简单

console.log(1)

setTimeout(function () {
    console.log(5)
}, 0)

Promise.resolve().then(function () {
    console.log(3)
}).then(function () {
    console.log(4)
})

console.log(2)

简单

console.log(1)

setTimeout(function () {
    console.log(5)
}, 10)

new Promise(resolve => {
    console.log(2)
    resolve()
    setTimeout(() => console.log(6), 10)
}).then(function () {
    console.log(4)
})

console.log(3)

太简单

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

太太简单

function fun1() {
    console.log(2)
}
function fun2() {
    console.log(1)
    fun1()
    console.log(3)
}
fun2()

没啥区别,还是简单

function func1() {
    console.log(1)
}
function func2() {
    setTimeout(() => {
        console.log(3)
    }, 0)
    func1()
    console.log(2)
}
func2()

简单。

// 注意,等号的右值是会执行的,别做多题,给做懵了
var p = new Promise(resolve => {
    console.log(1)
    resolve(4)
})
function func1() {
    console.log(2)
}
function func2() {
    setTimeout(() => {
        console.log(5)
    }, 0)
    func1()
    console.log(3)
    p.then(resolve => {
        console.log(resolve)
    })
}
func2()

不能说难,因为我需要用到草稿纸。

console.log(1)
const interval = setInterval(() => {
    console.log('4-9')
}, 0)
setTimeout(() => {
    console.log(5)
    Promise.resolve()
        .then(() => {
            console.log(7)
        })
        .then(() => {
            console.log(8)
        })
        .then(() => {
            setTimeout(() => {
                console.log(10)
                Promise.resolve()
                    .then(() => {
                        console.log(11)
                    })
                    .then(() => {
                        console.log(12)
                    })
                    .then(() => {
                        console.log(13)
                        clearInterval(interval)
                    })
            }, 0)
        })

    console.log(6)
}, 0)
Promise.resolve().then(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})

看到前面说了那么多简单,你可能会疑惑,难道我就没遇到过特别难的吗?

有!那就是在多个不同 then 链,而且一个有返回 resolve,一个没有返回的题目。
这类题目我只知道它会多延迟了一轮。

先看看常规的:

console.log(1);
Promise.resolve().then(() => {
    console.log(3)
}).then(() => {
    console.log(5)
}).then(() => {
    console.log(7)
}).then(() => {
    console.log(9)
})

Promise.resolve().then(() => {
    console.log(4)
}).then(() => {
    console.log(6)
}).then(() => {
    console.log(8)
}).then(() => {
    console.log(10)
})
console.log(2);

然后先简单变一下,现在还没上主菜

console.log(1);
Promise.resolve()
    .then(() => Promise.resolve())
    .then(() => console.log('4'))
Promise.resolve()
    .then(() => {Promise.resolve()})
    .then(() => console.log('3'))

console.log(2);

好了,前面两道题你很容易作对,但下面这道题你可能就懵了!
讲真,我也懵,虽然看过后我能答出来,但我解释不出。有兴趣的话可以查看 promise-order-invocation
唯一能想到的就是,它和 setTimeout 的延迟一样,最小是 4ms。

Promise.resolve()
    .then(() => {
        console.log(1)
        return Promise.resolve()
    }).then(() => {
        console.log(5)
    })
Promise.resolve()
    .then(() => {
        console.log(2)
    }).then(() => {
        console.log(3) // 这里比 5 提前输出,相信很容易理解
    }).then(() => {
        console.log(4) // 但重点在于,这里也会比 5 提前输出!
    }).then(() => {
        console.log(6)
    })

再来一道题烧烧脑子

console.log(1);
Promise.resolve().then(() => {
    console.log(3)
    return Promise.resolve()
}).then(() => {
    console.log(6)
    return Promise.resolve()
}).then(() => {
    console.log(8)
})

Promise.resolve().then(() => {
    console.log(4)
}).then(() => {
    console.log(5)
    return Promise.resolve()
}).then(() => {
    console.log(7)
})
console.log(2);

最后考考眼力,然后本篇文章就结束了

Promise.resolve()
    .then(() => Promise.resolve())
    .then(() => {Promise.resolve()})
    .then(() => {Promise.resolve()})
    .then(() => {
        console.log(2)
    })
Promise.resolve()
    .then(() => {Promise.resolve()})
    .then(() => {Promise.resolve()})
    .then(() => {Promise.resolve()})
    .then(() => {
        console.log(1)
    })
  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值