# 浏览器中的 JavaScript 线程
- 我们经常会说 JavaScript 是单线程的,但是 JavaScript 的线程应该有自己的容器进程:浏览器或者 Node。
- 浏览器是一个进程吗,它里面只有一个线程吗?
- 目前多数的浏览器其实都是多进程的,当我们打开一个 tab 页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
- 每个进程中又有很多的线程,其中包括执行 JavaScript 代码的线程;
- JavaScript 的代码执行是在一个单独的线程中执行的:
- 这就意味着 JavaScript 的代码,在同一个时刻只能做一件事;
- 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;
- 所以真正耗时的操作,实际上并不是由 JavaScript 线程在执行的:
- 浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作;
- 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;
# 宏任务和微任务
- 但是事件循环中并非只维护着一个队列,事实上是有两个队列:
- 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM 监听、UI Rendering 等
- 微任务队列(microtask queue):Promise 的 then 回调、 Mutation Observer API、queueMicrotask () 等
- 那么事件循环对于两个队列的优先级是怎么样的呢?
- 1.main script 中的代码优先执行(编写的顶层 script 代码);
- 2. 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
- 也就是宏任务执行之前,必须保证微任务队列是空的;
- 如果不为空,那么就优先执行微任务队列中的任务(回调);
# 事件循环面试题一
-
事件循环面试题
面试题 setTimeout(function () {
console.log("setTimeout1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("setTimeout2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
下面正确的的输出结果是:A
初始任务队列:
main script:promise1 回调函数 2
microtask: Promise1 queueMicrotask Promise2
macrotask:setTimeout1 setTimeout2
# 事件循环面试题二
-
事件循环面试题
面试题 async function bar() {
console.log("樱岛麻衣")
return new Promise(resolve => resolve())
}
async function foo() {
console.log("入间同学")
await bar()
console.log("稚名真白")
}
foo()
console.log("萌王")
下面正确的的输出结果是:C
# 事件循环面试题三
-
事件循环面试题
面试题 async function async1 () {
console.log('async1Start')
await async2();
console.log('async1End')
}
async function async2 () {
console.log('async2')
}
console.log('scriptStart')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise (function (resolve) {
console.log('promise1')
resolve();
}).then (function () {
console.log('promise2')
})
console.log('scriptEnd')
下面正确的的输出结果是:B
# 事件循环面试题四
-
事件循环面试题
面试题 Promise.resolve().then(() => {
console.log('樱岛麻衣')
return '稚名真白'
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log('和泉纱雾')
}).then(() => {
console.log('四系乃')
}).then(() => {
console.log('鸢一折纸')
}).then(() => {
console.log('牧濑红莉栖')
}).then(() =>{
console.log('小鸟游六花')
})
下面正确的的输出结果是:A
# 事件循环面试题五
-
事件循环面试题
面试题 Promise.resolve().then(() => {
console.log('樱岛麻衣')
return {
then: (resolve) => {
resolve('稚名真白')
}
}
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log('和泉纱雾')
}).then(() => {
console.log('四系乃')
}).then(() => {
console.log('鸢一折纸')
}).then(() => {
console.log('牧濑红莉栖')
}).then(() =>{
console.log('小鸟游六花')
})
下面正确的的输出结果是:C
thenable 会推迟一步微任务
# 事件循环面试题六
-
事件循环面试题
面试题 Promise.resolve().then(() => {
console.log('樱岛麻衣')
return Promise.resolve('稚名真白')
}).then(res => console.log(res))
Promise.resolve().then(() => {
console.log('和泉纱雾')
}).then(() => {
console.log('四系乃')
}).then(() => {
console.log('鸢一折纸')
}).then(() => {
console.log('牧濑红莉栖')
}).then(() =>{
console.log('小鸟游六花')
})
下面正确的的输出结果是:C
Promise 会推迟两步微任务
# Node 事件循环的阶段
- 我们最前面就强调过,事件循环像是一个桥梁,是连接着应用程序的 JavaScript 和系统调用之间的通道:
- 无论是我们的文件 IO、数据库、网络 IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
- 事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
- 但是一次完整的事件循环 Tick 分成很多个阶段:
- 定时器(Timers):本阶段执行已经被 setTimeout () 和 setInterval () 的调度回调函数。
- 待定回调(Pending Callback):对某些系统操作(如 TCP 错误类型)执行回调,比如 TCP 连接时接收到 ECONNREFUSED。
- idle, prepare:仅系统内部使用。
- 轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
- 检测(check):setImmediate () 回调函数在这里执行。
- 关闭的回调函数:一些关闭的回调函数,如:socket.on ('close', ...)
# Node 的宏任务和微任务
- 我们会发现从一次事件循环的 Tick 来说,Node 的事件循环更复杂,它也分为微任务和宏任务:
- 宏任务(macrotask):setTimeout、setInterval、IO 事件、setImmediate、close 事件;
- 微任务(microtask):Promise 的 then 回调、process.nextTick、queueMicrotask;
- 但是,Node 中的事件循环不只是 微任务队列和 宏任务队列:
- 微任务队列:
- next tick queue:process.nextTick;
- other queue:Promise 的 then 回调、queueMicrotask;
- 宏任务队列:
- timer queue:setTimeout、setInterval;
- poll queue:IO 事件;
- check queue:setImmediate;
- close queue:close 事件;
- 微任务队列:
- Node 事件循环的顺序
- next tick microtask queue
- pother microtask queue
- ptimer queue
- ppoll queue
- pcheck queue
- pclose queue
# 事件循环面试题七
-
Node 事件循环面试题
面试题 async function async1() {
console.log('async1Start')
await async2()
console.log('async1End')
}
async function async2() {/
console.log('async2')
}
console.log('scriptStart')
setTimeout(function () {
console.log('setTimeout1')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'))
process.nextTick(() => console.log('nextTick1'))
async1();
process.nextTick(() => console.log('nextTick2'))
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('scriptEnd')
setTimeout(() => {
console.log('setTimeout3')
}, 1000)
下面正确的的输出结果是:C
初始队列
mainScript:scriptStart async1Start async2 promise1 promise2 scriptEnd
nextTick:nextTick1 nextTick2
otherMicro:async1End promise3
timers:setTimeout1 300ms -> setTimeout2 1000ms -> setTimeout3
check:setImmediate
# 事件循环面试题八
- Node 事件循环面试题下面正确的的输出结果是:D
面试题 setTimeout(function () {
console.log('setTimeout1')
}, 1)
setTimeout(() => {
console.log('setTimeout2')
}, 2)
setTimeout(function () {
console.log('setTimeout3')
}, 3)
setTimeout(function () {
console.log('setTimeout4')
}, 1)
setImmediate(() => console.log('setImmediate'))
经过多次测试,发现出现了多种答案,这里面涉及的比较复杂,目前无法解答,建议问度娘