前言
javascript是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互,单线程是必要的,也是javascript这门语言的基石,原因之一在其最初也是最主要的执行环境——浏览器中,我们需要进行各种各样的dom操作。试想一下 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个dom,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。
单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。
而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
运行环境
js运行环境总共两个:浏览器和node。
概念:
javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。
js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈。
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。
被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)
运行机制
看图加深记忆 image1.一个主进程就是js引擎,其他均为辅助的线程。
2.主进程存在一个执行栈,事件触发线程维护一个消息队列
3.同步任务在执行栈中执行,异步任务在满足条件后加入到消息队列中,等待执行。
4.先执行栈中的任务,执行完毕后,检查队列是否为空,不为空,将队列中的任务压入执行栈中执行。直到栈和队列均为空。
测试一下:
setTimeout(function(){
console.log(0)
},500)
console.log(a);
上面这段代码用于不会有输出,同步代码死循环阻塞了执行栈。虽然定时后回调加入执行队列,但是异步永远不会执行。
微任务(microtask jobs)和宏任务(macrotask task)
浏览器环境下宏任务:setTimeout setImmediate MessageChannel 微任务:Promise.then MutationObserver
node环境下的微任务和宏任务有哪些 宏任务:setTimeout setImmediate 微任务:Promise.then process.nextTick
运行机制:
1.微任务在宏任务之前的执行,先执行 执行栈中的内容 执行后 清空微任务
2.每次取一个宏任务 就去清空微任务,之后再去取宏任务
测试一下
setTimeout(function(){
console.log('setTimeout1');
Promise.resolve().then(()=>{
console.log('then1');
});
},0)
Promise.resolve().then(()=>{
console.log('then2');
Promise.resolve().then(()=>{
console.log('then3');
})
setTimeout(function(){
console.log('setTimeout2');
},0)
})
//then2 then3 setTimeout1 then1 setTimeout2
node的下的执行特点
1.为微任务,定时器,io,setImmidiate分别分配消息队列
2.先检查定时器队列,如果有内容,则全部清空
3.从时间队列切换到io队列的过程中,检查微任务,如果有则情况微任务。
4.io队列执行完成,如果有check队列的内容,则执行。否则继续检查定时器队列,完成闭环。
注意⚠️
1.同样是微任务,process.nextTick,优于promise.then先执行
2.同样是宏任务,setTimeout和setImediate执行的先后顺序是不确定的,依赖于执行栈执行的速度。
网友评论