【浅谈JS执行机制】
八、JS的执行机制
8.1 进程和线程的概念
- 进程和线程的概念:
- 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程)
- 线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称之为主线程
- 所以我们也可以说进程是线程的容器

8.2 JavaScript的单线程
-
JavaScript是一门**单线程的语言,执行JavaScript代码只在一个单独的线程中执行
- 也就是说,同一个时间只能做一件事
-
单线程意味着:如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,如果有其中一个任务执行时间过久,就会导致页面堵塞
-
为了解决单线程问题,js将执行的任务分成了 同步任务 和 异步任务
-
而其中异步任务又分为了宏任务和微任务
8.3 同步任务和异步任务
-
同步任务
-
同步任务是指在主线程(执行栈)上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务
-
当我们打开网站时,网站的渲染过程、元素的渲染,其实就是一个同步任务
-
-
异步任务
-
异步任务不进入主线程(执行栈)、而是先进入异步进程处理,当异步任务执行完毕就会进入**“任务队列”**中
-
当主线程上的任务执行完毕,就会去任务队列中取出异步任务,然后放在主线程上执行,这个过程称为’事件循环’
-
像回调函数,什么时候被回调,这就是一个异步任务
-
-
总结:JavaScript中优先执行所有的同步任务,当同步任务执行完成后,在执行异步任务
8.4 JS的执行机制流程图

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
(4)主线程不断重复上面的第三步。
8.5 宏任务和微任务
-
异步队列中要执行的任务又分为了两类:宏任务和微任务
-
宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
-
微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()等
-
-
那么异步队列中宏任务和微任务的优先级又是怎样的?
- 先执行同步任务
- 执行异步队列中的微任务队列
- 当微任务队列执行完毕后,在执行宏任务队列
-
宏任务微任务补充:
- 可以将await关键字后面执行的代码,看做是包裹在
(resolve,reject) => {函数执行}中的代码 - awiat的下一条语句,可以看做是
then(res => {函数执行})中的代码 - 总结:await()这个过程是同步的,但是await的下一条语句会放到异步队列的微任务中
- 注:使用
async声明的函数,在调用时,依旧是同步任务,不会变成异步任务
- 可以将await关键字后面执行的代码,看做是包裹在
-
宏任务和微任务面试题


8.6 循环中绑定事件
-
代码如上图所示,问题:为什么打印是3?
-
首先,我们需要知道JS中执行任务的顺序(同步任务和异步任务),优***先执行同步任务,然后在执行异步任务***
-
js中所有的事件绑定所对应的回调函数都是异步任务
-
当绑定onclick事件后,不需要等待执行,继续执行下一个循环任务,每进行一次循环任务,全局变量的i都在不断的变化
-
当点击的时候,外层循环已经结束,然后执行 console.log(i) 时,由于i不是私有变量,便会找到上一级window作用域全局的i,最终打印的i会指向全局变量的i,所以输出是3
-
-
问题:为什么会出现上图中的情况?
-
在for循环中使用var声明的变量是没有块级作用域的,只有全局作用域,也就是说每一次循环,用var声明的变量不会保存到本次循环中,而是保存到全局作用域
-
模拟上图中for循环的基本流程
-
-
-
-
问题:为什么使用let声明变量可以解决问题?
-
使用{}包裹起来的代码我们称之为代码块,{}里面就是块级作用域
-
let声明的变量具有块级作用域,也就是说每一次循环 使用let声明的变量 会保存到本次循环体中,而本次循环体就是一个块级作用域
-
当访问变量时,优先访问自身作用域的下的变量,所以此时的i并不是外部全局变量下的i
-
模拟for循环中使用let初始化变量的流程:
-
8.7 循环中按顺序处理异步任务
如果希望在循环中按顺序依次处理异步任务,最好使用 await + for 循环,而不是 await + forEach循环
-
使用await + for循环可以按照顺序依次处理异步任务

坑:使用await + forEach循环***无法*** 按照顺序依次处理异步任务
-
首先需要了解forEach的实现原理
-
由下图可知,forEach方法中传入的回调函数是按顺序依次执行的
-
但是,即使回调函数函数体中有await,也不会阻止回调函数的执行
-
await只会阻塞当前函数体内代码的执行
-
