深入理解 JS 引擎的执行机制
1. 灵魂三问 : JS 为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢?
2.JS 中的 event loop(1)
3.JS 中的 event loop(2)
4. 说说 setTimeout
首先, 请牢记 2 点:
(1) JS 是单线程语言
(2) JS 的 Event Loop 是 JS 的执行机制. 深入了解 JS 的执行, 就等于深入了解 JS 里的 event loop
1. 灵魂三问 : JS 为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢?
技术的出现, 都跟现实世界里的应用场景密切相关的.
同样的, 我们就结合现实场景, 来回答这三个问题
(1) JS 为什么是单线程的?
JS 最初被设计用在浏览器中, 那么想象一下, 如果浏览器中的 JS 是多线程的.
场景描述:
那么现在有 2 个进程, process1 process2, 由于是多进程的 JS, 所以他们对同一个 dom, 同时进行操作
process1 删除了该 dom, 而 process2 编辑了该 dom, 同时下达 2 个矛盾的命令, 浏览器究竟该如何执行呢?
这样想, JS 为什么被设计成单线程应该就容易理解了吧.
(2) JS 为什么需要异步?
场景描述:
如果 JS 中不存在异步, 只能自上而下执行, 如果上一行解析时间很长, 那么下面的代码就会被阻塞.
对于用户而言, 阻塞就意味着 "卡死", 这样就导致了很差的用户体验
所以, JS 中存在异步执行.
(3) JS 单线程又是如何实现异步的呢?
既然 JS 是单线程的, 只能在一条线程上执行, 又是如何实现的异步呢?
是通过的事件循环 (event loop), 理解了 event loop 机制, 就理解了 JS 的执行机制
2.JS 中的 event loop(1)
例 1, 观察它的执行顺序
- console.log(1)
- setTimeout(function(){
- console.log(2)
- },0)
- console.log(3)
运行结果是: 1 3 2
也就是说, setTimeout 里的函数并没有立即执行, 而是延迟了一段时间, 满足一定条件后, 才去执行的, 这类代码, 我们叫异步代码.
所以, 这里我们首先知道了 JS 里的一种分类方式, 就是将任务分为: 同步任务和异步任务
图片描述
按照这种分类方式: JS 的执行机制是
首先判断 JS 是同步还是异步, 同步就进入主进程, 异步就进入 event table
异步任务在 event table 中注册函数, 当满足触发条件后, 被推入 event queue
同步任务进入主线程后一直执行, 直到主线程空闲时, 才会去 event queue 中查看是否有可执行的异步任务, 如果有就推入主进程中
以上三步循环执行, 这就是 event loop
所以上面的例子, 你是否可以描述它的执行顺序了呢?
console.log(1) 是同步任务, 放入主线程里
setTimeout() 是异步任务, 被放入 event table, 0 秒之后被推入 event queue 里
console.log(3 是同步任务, 放到主线程里
当 1, 3 在控制条被打印后, 主线程去 event queue(事件队列) 里查看是否有可执行的函数, 执行 setTimeout 里的函数
我自己是一名从事了多年开发的 web 前端老程序员, 目前辞职在做自己的 Web 前端私人定制课程, 今年年初我花了一个月整理了一份最适合 2019 年学习的 Web 前端学习干货, 各种框架都有整理, 送给每一位前端小伙伴, 想要获取的可以添加我的 Web 前端交流群 600610151, 即可免费获取.
3.JS 中的 event loop(2)
所以, 上面关于 event loop 就是我对 JS 执行机制的理解, 直到我遇到了下面这段代码
例 2:
- setTimeout(function(){
- console.log('定时器开始啦')
- });
- new Promise(function(resolve){
- console.log('马上执行 for 循环啦');
- for(var i = 0; i < 10000; i++){
- i == 99 && resolve();
- }
- }).then(function(){
- console.log('执行 then 函数啦')
- });
- console.log('代码执行结束');
尝试按照, 上文我们刚学到的 JS 执行机制去分析
setTimeout 是异步任务, 被放到 event table
new Promise 是同步任务, 被放到主进程里, 直接执行打印 console.log('马上执行 for 循环啦')
.then 里的函数是 异步任务, 被放到 event table
console.log('代码执行结束') 是同步代码, 被放到主进程里, 直接执行
所以, 结果是 [马上执行 for 循环啦 --- 代码执行结束 --- 定时器开始啦 --- 执行 then 函数啦] 吗?
亲自执行后, 结果居然不是这样, 而是 [马上执行 for 循环啦 --- 代码执行结束 --- 执行 then 函数啦 --- 定时器开始啦]
那么, 难道是异步任务的执行顺序, 不是前后顺序, 而是另有规定? 事实上, 按照异步和同步的划分方式, 并不准确.
而准确的划分方式是:
macro-task(宏任务): 包括整体代码 script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
clipboard.PNG
按照这种分类方式: JS 的执行机制是
执行一个宏任务, 过程中如果遇到微任务, 就将其放到微任务的 [事件队列] 里
当前宏任务执行完成后, 会查看微任务的 [事件队列] , 并将里面全部的微任务依次执行完
重复以上 2 步骤, 结合 event loop(1) event loop(2) , 就是更为准确的 JS 执行机制了.
尝试按照刚学的执行机制, 去分析例 2:
首先执行 script 下的宏任务, 遇到 setTimeout, 将其放到宏任务的 [队列] 里
遇到 new Promise 直接执行, 打印 "马上执行 for 循环啦"
遇到 then 方法, 是微任务, 将其放到微任务的 [队列里]
打印 "代码执行结束"
本轮宏任务执行完毕, 查看本轮的微任务, 发现有一个 then 方法里的函数, 打印 "执行 then 函数啦"
到此, 本轮的 event loop 全部完成.
下一轮的循环里, 先执行一个宏任务, 发现宏任务的 [队列] 里有一个 setTimeout 里的函数, 执行打印 "定时器开始啦"
所以最后的执行顺序是 [马上执行 for 循环啦 --- 代码执行结束 --- 执行 then 函数啦 --- 定时器开始啦]
4. 谈谈 setTimeout
这段 setTimeout 代码什么意思? 我们一般说: 3 秒后, 会执行 setTimeout 里的那个函数
- setTimeout(function(){
- console.log('执行了')
- },3000)
但是这种说并不严谨, 准确的解释是: 3 秒后, setTimeout 里的函数被会推入 event queue, 而 event queue(事件队列) 里的任务, 只有在主线程空闲时才会执行.
所以只有满足 (1)3 秒后 (2) 主线程空闲, 同时满足时, 才会 3 秒后执行该函数
如果主线程执行内容很多, 执行时间超过 3 秒, 比如执行了 10 秒, 那么这个函数只能 10 秒后执行了.
好啦, 此次分享到这里就结束了.
来源: http://www.jianshu.com/p/3c2c2b0a4d66