1.在讨论浏览器与JavaScript之前,我们先来简单了解一下进程与线程
进程(process):资源分配的最小单位
进程是应用程序的执行实例,是操作系统进行资源分配和调度的一个独立单位。
线程(thread):CPU调度的最小单位
线程是进程内部的一个执行单元,是被系统独立调度和分派的基本单位。系统创建好进程后,实际上就启动执行了该进程的主执行线程。
上面这样讲可能不是很容易理解,我们以工厂🏭模式来比喻:
- 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂🏭,时刻在运行着。
- 单个CPU一次只能运行一个任务
- 进程就好比工厂的车间🚗,它代表CPU所能处理的单个任务
- 一个车间里,可以有很多工人👷。他们协同完成一个任务
- 线程就好比车间里的工人👷,一个进程可以包含多个线程
2.浏览器多线程与Javascript单线程(单线程与多线程)
首先浏览器是多线程的,所以浏览器一次能够处理多个事件,比如渲染页面,脚本执行,事件处理等。
JavaScript
是单线程的(浏览器只给JS分配了一个线程)
JavaScript单线程
单线程的特点就是一次只能处理一件事情。后一个任务需要等待前一个任务执行完再执行。
JS在单线程中实现异步机制只要依靠浏览器的任务队列
,任务队列分为同步任务队列
与异步任务队列
,异步任务又可以分为宏任务
与微任务
在同步任务自上而下执行时,如果遇到一个异步任务,不会立即执行,而是把它放到异步任务队列中去排队,当同步任务执行完成后,才会到异步任务队列进行查找等待任务队列中的内容(同步任务队列完不成,不管异步任务是否达到时间,都不执行),等达到条件后执行,然后再接着去异步任务队列中 查找。这就是因为js是单线程的,一次只能处理一件事情
JavaScript为什么要设计成单线程
JavaScript
之所以采用单线程,而不是多线程,跟它的历史有关。最开始的JavaScript功能并没有现在这么强大,作为浏览器脚本语言,它最初主要是用来处理页面的用户交互
,以及DOM的操作
。如果JavaScript
是多线程的话,假设存在两个线程同时操作一个DOM,这时候浏览器就不知道应该处理哪个线程的操作结果了。
3.同步与异步任务
同步: 在一个线程上同一时间只能做一件事情,当情事情做完才能进行下一个任务。
异步: 在主栈中执行一个任务,但是发现这个任务是一个异步的操作,会将它放到异步任务队列中。
异步任务又分为宏任务与微任务:
宏任务: 定时器setTimeout
,setInterval
,事件绑定
,回调函数
,node中的fs模块
微任务: new Promise().then(回调)
,process.nextTick()
,async await
4.执行栈与任务队列
1)执行栈:从名字可以看出,执行栈使用到的是数据结构中的栈结构, 它是一个存储函数调用的栈结构,遵循先进后出的原则。它主要负责跟踪所有要执行的代码。 每当一个函数执行完成时,就会从堆栈中弹出(pop)该执行完成函数;如果有代码需要进去执行的话,就进行 push 操作。
2)任务队列: 从名字中可以看出,任务队列使用到的是数据结构中的队列结构,它用来保存异步任务,遵循先进先出的原则。它主要负责将新的任务发送到队列中进行处理。
执行顺序:
JavaScript在执行代码时,会将同步的代码按照顺序排在执行栈中,然后依次执行里面的函数。当遇到异步任务时,就将其放入任务队列中,等待当前执行栈所有同步代码执行完成之后,就会从异步任务队列中取出已完成的异步任务的回调并将其放入执行栈中继续执行,如此循环往复,直到执行完所有任务
先执行同步任务,执行完接着执行微任务,最后执行宏任务。这个过程会不断重复
下面看几道经典面试题
1 | console.log("script start"); |
解析:
1.首先执行同步任务:打印script start,async2 end,Promise,script end
2.同步任务执行完,开始执行微任务:async1 end,promise1,promise2
3.最后执行宏任务:setTimeout