在开始讲解之前,我们先来看一段代码:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
});
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
});
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
});
  各位小伙伴觉得上面的结果输出会是多少呢?如果你没有了解过javascript的执行机制的话,上面的题目可能会让你崩溃。
不过别着急,先往下看,我保证你看到最后,能轻轻松松写出上面代码的答案,并且完全了解其中的原理。
​   首先,希望大家记住一个要点,javascript是单线程的语言。
  因此,所有的javascript的异步特性都是基于单线程实现的,记住了这个特点,我们再去理解javascript的很多机制就容易很多了。
我们先从简单的代码说起,来引出今天的概念。
console.log('程序开始执行~');
setTimeout(() => {
console.log('执行setTimeout~');
}, 1000);
console.log('程序执行结束~');
// 输出结果:
// 程序开始执行~
// 程序执行结束~
// ...1s(这里表示等待时间)
// 执行setTimeout~
  我想小伙伴们对上面结果都不会有疑问,setTimeout是我们常用来做延迟执行的全局函数。它接受两个参数,要执行的函数a和等待的秒数x,函数a会在程序经过x秒后执行。
  这里引出我们的第一个概念:同步函数和异步函数。上面的函数a就是异步函数了,它不是立刻执行的函数,而是要等待一段时间,或者说满足一定的条件之后才执行的函数。
  不过,有时候我们明明设置了3秒的定时,但是却发现函数并没有在3秒后执行,有时候会更久,这又是为什么呢?
  这要从javascript的执行原理说起,js执行的时候,有一个专门存放异步函数的地方,称之为Event Table,而当异步函数已经满足回调的执行条件之后(比如时间过了x秒,异步请求返回了结果等等),原本放在Event Table的异步函数就会被放进一个队列中,这个队列称为Event Queue。
  不要觉得这个队列很深奥,其实就是一个排队,里面放的都是回调函数,它们正一个个等待着按顺序执行自身呢。来看下面的代码:
​console.log('程序执行开始~')
setTimeout(() => {
console.log('setTimeout执行啦~')
}, 3000);
sleep(5000);
console.log('程序执行结束~')
// 注:这里的sleep函数不是js的标准函数,只是表示一个执行需要5秒的函数。
// 输出结果:
// 程序执行开始~
// ...5s后
// 程序执行结束~
// setTimeout执行啦~
  从上面结果我们可以看出,setTimeout并非是在setTimeout调用之后经过3秒就马上输出结果"setTimeout执行啦~",而是等待下方的sleep函数执行完毕后才输出的结果。
  前面我已经说过,要牢记javascript是单线程,那么它就一次只能运行一个一段代码。
  因此,即使处于异步队列的setTimeout函数已经满足执行条件了,但是它还是得等待在Event Queue中,直到主线程执行完毕才能执行。
  所以请记住,js会先执行主线程的同步代码,遇到setTimeout就将其回调函数注册在Event Table中,然后当异步函数满足执行条件之后,就会被放入Event Queue中,但是并不能马上执行,而是得等待主线程剩余代码执行完毕,队列中的函数才能按顺序执行。
 

  
  我想小伙伴们看到这里,已经明白一点js的执行机制了,那么我们一鼓作气,继续深入一下(其实也很简单),Promise和process又是怎样的执行机制呢?
在放代码之前,我先介绍两个基本概念:
  • process.nextTick,我们知道浏览器环境下的setTimeout,那么process.nextTick就相当于在node环境下执行的setTimeout。
  • 宏任务和微任务,主线程一直在执行script代码,还有setTimeout、setInterval函数就是宏任务,而Promise.then,process.nextTick则是微任务。
  接下来,我们看一段代码:
console.log('程序执行开始~')
setTimeout(() => {
console.log('setTimeout执行啦~')
}, 3000);
new Promise((resolve) => {
console.log('promise开始执行~');
resolve();
}).then(() => {
console.log('promise执行结束~')
});
console.log('程序执行结束~')
// 输出结果:
// 程序执行开始~
// promise开始执行~
// 程序执行结束~
// promise执行结束~
// ...3s后
// setTimeout执行啦~
  emmm,这里的结果是不是就有点微妙了。
  记得我们刚才说的宏任务和微任务吗,js的执行机制中,先是执行完宏任务中的同步代码,接着执行微任务,接着执行宏任务的异步代码。这样说可能有点绕,我们结合上面的代码来看。
  • 代码一开始执行,执行的就是全局代码,也就是宏任务的同步代码;
  • 遇到console.log,直接执行,输出"程序执行开始~";
  • 接着执行,遇到setTimeout函数,将其回调函数注册进宏任务的Event Queue(注意:宏任务和微任务分别有自己的Event Queue);
  • 接着遇到new Promise,立刻执行(new Promise里面的函数是立刻执行的,只有.then函数里面才是放到微任务去执行的,不要搞混咯~),输出"promise开始执行~";
  • 接着遇到promise.then函数,将其回调函数注册到微任务的Event Queue;
  • 接着继续执行,遇到console.log,直接输出"程序执行结束~"
  • 到这里,宏任务的同步代码就全部执行完毕了,这时候,js引擎会去检查微任务的Event Queue中是否存在回调函数,这时微任务的Queue中还有一个函数未执行,因此在这时候执行,输出"promise执行结束~";
  • 当微任务的所有回调函数被执行完了之后,一次事件循环就结束了。
  • 这时候js引擎会检查宏任务的Event Queue中是否还有未执行的函数,如果还有,将会开启下一轮的事件循环。由于此时我们宏任务的Event Queue中还有未执行的setTimeout,所以开启下一轮事件循环,执行setTimeout回调,输出"setTimeout执行啦~"
 

 
  能坚持到这里的小伙伴,相信你已经学到了不少,给自己点个赞吧
  接下来,我们再来看一下加上process.nextTick之后的一个例子:
console.log('1')
setTimeout(() => {
console.log('2')
})
new Promise((resolve) => {
console.log('3')
resolve()
}).then(() => {
console.log('4')
})
process.nextTick(() => {
console.log('5')
})
new Promise((resolve) => {
console.log('6')
resolve()
}).then(() => {
console.log('7')
})
process.nextTick(() => {
console.log('8')
})
console.log('9')
// 输出结果
// 1 3 6 9 5 8 4 7 2
  是不是有一点一开始那块代码的味道了,上面的输出结果也很容易理解:先是执行了同步代码,输出:1 3 6 9,然后输出微任务中的process.nextTick的回调:5 8,然后输出Promise.then中的回调:4 7,最后输出setTimeout的2,是不是一目了然。
上面唯一要注意点的就是:process.nextTick是要比Promise.then先执行的(也许不同node版本环境下不同,这个要看具体执行结果)。
 

 
  好啦!终于这篇文章也要接近尾声了,还在看的小伙伴再给自己点个赞吧,当然也可以给我点个赞~你每一个小小的支持都是我坚持下去的最大动力。
接下来要进入最后的重头戏,按照我们前面所讲的知识,分析刚开始的代码的执行结果。这里再贴下一开始的代码,最终结果我会在文章最后再贴出来,所以小伙伴们也可以自己先看下,最后比对结果是否和文中的一致。
console.log('1');
setTimeout(() => {
console.log('2');
process.nextTick(() => {
console.log('3');
})
new Promise((resolve) => {
console.log('4');
resolve();
}).then(() => {
console.log('5')
})
});
process.nextTick(() => {
console.log('6');
})
new Promise((resolve) => {
console.log('7');
resolve();
}).then(() => {
console.log('8')
});
setTimeout(() => {
console.log('9');
process.nextTick(() => {
console.log('10');
})
new Promise((resolve) => {
console.log('11');
resolve();
}).then(() => {
console.log('12')
})
});
接下来是分析过程:
  • 程序开始,执行宏任务同步代码,遇到console.log,输出:;
  • 遇到setTimeout1,将其放入宏任务Event Queue中;
  • 遇到process.nextTick1,放入微任务Event Queue中;
  • 遇到new Promise,直接执行其中的代码,输出:;
  • 遇到Promise.then1函数,将其放入微任务Event Queue;
  • 继续执行,遇到setTimeout2,放入宏任务Event Queue;
  • 此时任务队列状态:
    • 宏Queue: setTimeout1,setTimeout2;
    • 微Queue: process.nextTick1、Promise.then1;
  • 至此,宏任务同步代码执行完毕,检测微任务队列是否存在任务,由于存在两个微任务,所以这时候执行微任务;
  • 先执行process.nextTick1,输出:;
  • 接着执行Promise.then1,输出: ;
  • 微任务执行完毕后,一次事件循环结束,js引擎持续检测宏任务中是否存在任务,存在的话开启下一次事件循环;由于存在两个setTimeout,所以在满足setTimeout执行条件后,开启下一次事件循环,执行回调函数;
  • 先执行setTimeout1,遇到console.log,输出:;
  • 接着遇到process.nextTick2,放入微任务Event Queue;
  • 继续执行遇到new Promise,直接执行,输出:;
  • 然后遇到Promise.then2,放入微任务Event Queue;
  • 至此setTimeout1执行完毕,此时任务队列状态:
    • 宏Queue: setTimeout2;
    • 微Queue: process.nextTick2、Promise.then2;
  • js引擎检查微任务Event Queue中还存在两个微任务,因此执行这两个微任务;
  • 先执行process.nextTick2,输出:;
  • 接着执行Promise.then2,输出:;
  • 微任务执行完毕,第二次事件循环结束;
  • js引擎持续检查宏任务Event Queue中是否还有未执行函数,检测到还有setTimeout2未执行,因此开启第三轮的事件循环;
  • 执行setTimeout2,遇到console.log,输出:;
  • 又遇到process.nextTick3,放入微任务队列;
  • 遇到new Promise,直接执行,输出:;
  • 遇到Promise.then3,放入微任务队列;
  • 至此,setTimeout2执行完毕,此时任务队列状态:
    • 宏Queue: 无;
    • 微Queue: process.nextTick3、Promise.then3;
  • js引擎在检测是否存在未执行的微任务,由于还有两个微任务未执行,因此将其执行;
  • 先执行process.nextTick3,输出:10;
  • 接着执行Promise.then3,输出:12;
  • 至此,微任务执行完毕,事件循环结束;
  最后程序输出结果:1 7 6 8 2 4 3 5 9 11 10 12
 

 
  看到这里的小伙伴们,给自己点第三个赞吧。
  怎么样,是不是觉得已经完全掌握了js的执行机制,其实宏任务和微任务除了上文提到的那些,还有一些其他的,可以下来自己再去了解下~
  最后,感谢大家的阅读,如果觉得文章写的还可以的话,可以给我点个赞、点个关注、或者直接关注本人,我会持续分享更多优质的技术文章,我们一起加油吧!
 

简单而面试中又常见的知识点:JS执行机制的更多相关文章

  1. 从一道看似简单的面试题重新理解JS执行机制与定时器

     壹 ❀ 引 最近在看前端进阶的系列专栏,碰巧看到了几篇关于JS事件执行机制的面试文章,因为我在之前一篇 JS执行机制详解,定时器时间间隔的真正含义 博文中也有记录JS执行机制,所以正好用于作为测试自 ...

  2. 浏览器中js执行机制学习笔记

    浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...

  3. 前端js面试中的常见的算法问题

    虽说我们很多时候前端很少有机会接触到算法.大多都交互性的操作,然而从各大公司面试来看,算法依旧是考察的一方面.实际上学习数据结构与算法对于工程师去理解和分析问题都是有帮助的.如果将来当我们面对较为复杂 ...

  4. Nginx面试中最常见的18道题 抱佛脚必备

    Nginx的并发能力在同类型网页服务器中的表现,相对而言是比较好的,因此受到了很多企业的青睐,我国使用Nginx网站的知名用户包括腾讯.淘宝.百度.京东.新浪.网易等等.Nginx是网页服务器运维人员 ...

  5. nginx面试中最常见的18道题

    1.请解释一下什么是Nginx? Nginx是一个web服务器和反向代理服务器,用于HTTP.HTTPS.SMTP.POP3和IMAP协议. 2.请列举Nginx的一些特性. Nginx服务器的特性包 ...

  6. 面试中linux常见的20个命令

    1.查找文件 find / -name filename.txt 根据名称查找/目录下的filename.txt文件. 2.查看一个程序是否运行 ps –ef|grep tomcat 查看所有有关to ...

  7. 企业面试中关于MYSQL重点的28道面试题解答

      问题1:char.varchar的区别是什么? varchar是变长而char的长度是固定的.如果你的内容是固定大小的,你会得到更好的性能. 问题2: TRUNCATE和DELETE的区别是什么? ...

  8. 深入理解 JS 引擎执行机制(同步执行、异步执行以及同步中的异步执行)

    首先明确两点: 1.JS 执行机制是单线程. 2.JS的Event loop是JS的执行机制,深入了解Event loop,就等于深入了解JS引擎的执行. 单线程执行带来什么问题? 在JS执行中都是单 ...

  9. 带你全面了解高级 Java 面试中需要掌握的 JVM 知识点

    目录 JVM 内存划分与内存溢出异常 垃圾回收算法与收集器 虚拟机中的类加载机制 Java 内存模型与线程 虚拟机性能监控与故障处理工具 参考 带你全面了解高级 Java 面试中需要掌握的 JVM 知 ...

随机推荐

  1. Nginx安装、多域名访问

    nginx web服务 apache iis django web框架 lvs 负载均衡 章文嵩博士 vue 尤雨溪 Tengine F5 硬件负载 A10 安装 ``` wget http://ng ...

  2. vue显示富文本

    来源:https://segmentfault.com/q/1010000013952512 用  v-html 属性解决

  3. apache虚拟主机配置-域名/IP和端口两种配置

    由于百度上有非常详细的资料,我这里就转载而已:https://jingyan.baidu.com/article/4f7d5712d48a191a201927e0.html

  4. Codeforces Round #635 (Div. 2) 题解

    渭城朝雨浥轻尘,客舍青青柳色新. 劝君更尽一杯酒,西出阳关无故人.--王维 A. Ichihime and Triangle 网址:https://codeforces.com/contest/133 ...

  5. 动画图解Git命令

    ​Git是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常大的项目版本管理,是目前使用范围最广的版本管理工具 尽管Git是一个非常强大的工具,但我认为大多数人都会同意我的说法,即它也可以 ...

  6. java中functional interface的分类和使用

    目录 简介 Functional Interface Function:一个参数一个返回值 BiFunction:接收两个参数,一个返回值 Supplier:无参的Function Consumer: ...

  7. Handler 机制(一)—— Handler的实现流程

    由于Android采用的是单线程模式,开发者无法在子线程中更新 UI,所以系统给我提供了 Handler 这个类来实现 UI 更新问题.本贴主要说明 Handler 的工作流程. 1. Handler ...

  8. js 运动函数篇 (一) (匀速运动、缓冲运动、多物体运动、多物体不同值运动、多物体多值运动)层层深入

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS写 匀速运动.缓冲运动.多物体运 ...

  9. vue2.x学习笔记(三十二)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12684060.html. 深入响应式原理 vue最独特的特性之一,是其非侵入式(耦合度低)的响应式系统:数据模型仅 ...

  10. redis- info调优入门-《每日五分钟搞定大数据》

    本文根据redis的info命令查看redis的内存使用情况以及state状态,来观察redis的运行情况以及需要作出的相应优化. info 1.memory used_memory:13409011 ...