1. JavaScript是单线程,非阻塞的
-
单线程:DOM渲染和执行JavaScript代码时,都只有一个主线程来处理所有的任务。
-
非阻塞: 当执行一项异步任务(无法立即返回结果,需要花费一定的时间,如AJAX请求, I/O事件)时,主线程会挂起(pending)这个任务,然后在异步返回结果的时候再根据一定的规则去执行相应的回调。
2. 执行栈和事件队列
当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。
-
执行栈:JS代码执行时,全局代码会创建一个全局执行环境(什么是执行环境),函数被调用时会创建一个函数执行环境。由于JS是单线程,一次只能执行一个函数,于是这些函数在执行时就会按照先进后出的顺序添加到执行栈中。
-
事件队列:异步代码执行时,不会等待它返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当异步事件返回结果时,JS会将这个事件加入与当前执行栈不同的另外一个队列,即事件队列中。事件队列中的回调函数不会立即执行,而是等待当前执行栈中所有的任务执行完毕后,主线程处于空闲状态时,再执行回调函数中的同步代码。如此反复的过程,就是事件循环。
宏任务(macrotast)和微任务(microtask)
根据异步事件的类型,这个事件会被放到宏任务或微任务中去。在当前执行栈为空时,主线程会查看微任务队列中是否有事件。
- 如果有,则依次执行全部微任务,如果微任务中产生了新的微任务,则继续执行微任务,直到所有微任务都执行完毕。然后再去宏任务队列中取出最前面的事件,然后执行其回调函数。
- 如果没有,则执行宏任务中的事件
当前执行栈执行完毕时,然后会开始下一轮的事件循环,即执行微任务队列中的全部事件,然后再执行下一个宏任务队列中的事件。
宏任务有:
- script中的同步代码
- setTimeout
- setInterval
- requestAnimationFrame
- setImmediate(Node.js中)
- UI Render
微任务有:
- Promise
- MutationObserve
- Process.nextTick(Node.js中)
3. 实现一个函数,接收一个函数作为参数,然后反序执行
逻辑: 在同步代码执行完后,主线程空闲状态,然后在执行异步回调。
let timer = null
const tasks = []
function fn(callback) {
if (timer) {
clearTimeout(timer)
}
tasks.unshift(callback)
timer = setTimeout(() => tasks.forEach(cb => cb()))
}
fn(() => console.log(1))
fn(() => console.log(2))
fn(() => console.log(3))
// 打印结果 321