以下这段代码的执行结果是什么?
//如果依照:js是按照语句出现的顺序执行这个理念,
//那么代码执行的结果应该是:
//先打印1,500ms后打印3,在过500ms后打印4,在过1000ms后打印2
//但结果并不是这样的,得到的结果是:
//控制台打印的顺序是1,2000ms后打印2,3,4
const useTime = t => {
let start = Date.now()
while(Date.now() - start < t) {}
}
let timer1 = setTimeout(() => {
console.log(3)
}, 500)
let timer2 = setTimeout(() => {
console.log(4)
}, 1000)
console.log(1)
useTime(2000)
console.log(2)
关于javascript
javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的。
javascript的同步和异步
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
1、同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
2、先执行执行栈中的同步任务。
3、当Event Table中指定的事情完成时,会将异步任务(回调函数)放入任务队列中。
4、一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( event loop)
5、我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
异步任务是通过回调函数实现的
普通事件,如 click、 resize等
资源加载,如load、 error等
定时器,包括 setInterval、 setTimeout等
异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)
因此在了解JS的运行机制后,我们再来分析下上面代码的执行流程
• 进入全局执行上下文
创建由第三方计时模块管理的timer1,timer1会在500ms后把任务fn1放入任务队列
创建由第三方计时模块管理的timer2,time2会在1000ms后把任务fn2放入任务队列
输出1
• 进入useTime执行上下文
执行代码耗时2000ms,退出useTime执行上下文
在500ms和1000ms时timer1和timer2各自完成投放,此操作不属于JS主线程
• 返回全局执行上下文 输出2 同步任务执行完毕
开始扫描任务队列 取出队列的fn1
• 进入fn1执行上下文
输出3,退出fn1
执行上下文 取出队列的fn2
• 进入fn2执行上下文
输出4,退出fn2执行上下文
循环扫描任务队列