前言
网页有各种讲js的单线程,和同步异步的.说真的,看半天我挺晕的,感觉本来没有多大的事,硬是被说的好复杂.
一、js为什么是单线程?
严谨的描述为: 浏览器中的js执行和UI渲染是在一个线程中顺序发生的。
js引擎在解析HTML生成DOM树的过程中,如果遇到js,主线程则会停止DOM的渲染,先去执行js,js解析完成,才会继续解析HTML.
为什么遇到js主线程,会停止执行html,等js执行完毕,才会继续执行html呢?
主线程在解析html,生成DOM树的过程中,会执行style,layout,render等操作,而js可以操作DOM,cssDOM,会影响主线程在解析HTML的最终渲染效果,最终的页面渲染和会变得不可控.
考虑到最终页面的渲染效果的一致性,所以js在浏览器中的实现,被设计成为了JS执行阻塞UI渲染型。
二、js代码执行过程
1.执行发生的事
js代码按照从上到下的物理顺序执行的时候,会形成执行栈,队列,和堆三部分东西.
堆(heap,存放对象,数据,垃圾回收就是这里)和栈(stack,存放基本数据类型),队列(queue 存放等待执行的任务)
栈分为执行栈和内存栈两种,内存栈只存变量,执行栈负责代码执行。执行栈,先进后出,直到栈被清空。它们之间就是卧室和厨房的关系。
2.一段代码到底怎么运行
1, js是单线程的,一次只能执行一个任务(任务即代码表达式),代码被执行必须放入执行栈.
2, 代码被分为同步任务和异步任务,同步任务会立即加入执行栈.
3, 异步任务,在物理顺序执行到它时,会被js引擎直接跳过.当异步任务有了结果就会放在队列上(task queque)(遵从先进先出).
4,等待执行栈为空时,通过event Loop 获取获取队列的第一个任务,放入执行栈.
5, js中分为执行栈和消息队列,当执行栈为空,则去消息队列中获取.消息队列分为,微任务队列和宏任务队列.
Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。
- 宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
- 微任务:process.nextTick, Promise, Object.observer, MutationObserver.
看下面代码的执行流程:
console.log(1, 'log')
new Promise(function (resolve) {
console.log(3)
resolve(100)
}).then(function (data) {
console.log(data, 'promise')
})
setTimeout(function () {
console.log(4, 'setTimeout');
})
console.log(2, 'log')
微任务和宏任务之间的选择
3 补充几个概念
队列
- 队列的特点就是先进先出(First In First Out ).
- 先开始的任务执行完后,才能开始执行后续任务.
- 所以的异步任务完成后都会加入队列中, 一旦当前执行栈空了,轮询就会取出消息队列的第一条,传入程序。程序开始执行对应的回调函数,等到执行完,再处理下一条消息。
事件轮询
- 所谓Event Loop机制,指的是一种内部循环,用来一轮又一轮地处理消息队列之中的消息,即执行对应的回调函数。
线程
- 主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。
- 当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环
总结
1 JS内存分为堆内存和栈内存
2 引用类型在栈中保存指针,在堆中保存对象值
3 所有JS代码运行,都需要放入执行栈中.执行代码前,会先创建执行上下文执行,上下文包含了三种(全局、函数、eval.
4 同步任务先执行,异步任务放队列.
5 微任务先执行,宏任务后执行.
6 微任务全部拉入执行栈,宏任务一次拉一个.
7 栈是先进后出,队列是先进先出.