所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心。可看HTML规范中的这段话:
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.
为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,用户引擎必须使用 event loops。Event Loop 包含两类:一类是基于 Browsing Context ,一种是基于 Worker ,二者是独立运行的。 下面本文用一个例子,着重讲解下基于 Browsing Context 的事件循环机制。
来看下面这段 JavaScript 代码:
setTimeout(function() { console.log('setTimeout'); }, 0);//前端全栈交流学习圈:866109386 //帮助1-3年前端人员,突破技术,提升思维 Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
先猜测一下这段代码的输出顺序是什么,再去浏览器控制台输入一下,看看实际输出的顺序和你猜测出的顺序是否一致,如果一致,那就说明,你对 JavaScript 的事件循环机制还是有一定了解的,继续往下看可以巩固下你的知识;而如果实际输出的顺序和你的猜测不一致,那么本文下面的部分会为你答疑解惑。
任务队列
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。具体的可以用下面的图来大致说明一下:
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
主线程重复执行上述步骤
可以用一张图来说明下流程:
这里相信有人会想问,什么是 microtasks ?规范中规定,task分为两大类, 分别是 Macro Task (宏任务)和 Micro Task(微任务), 并且每个宏任务结束后, 都要清空所有的微任务,这里的 Macro Task也是我们常说的 task ,有些文章并没有对其做区分,后面文章中所提及的task皆看做宏任务( macro task)。
(macro)task 主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
microtask主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
setTimeout/Promise 等API便是任务源,而进入任务队列的是由他们指定的具体执行任务。来自不同任务源的任务会进入到不同的任务队列。其中 setTimeout 与 setInterval 是同源的。
分析示例代码
千言万语,不如就着例子讲来的清楚。下面我们可以按照规范,一步步执行解析下上面的例子,先贴一下例子代码(免得你往上翻)。
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0);//前端全栈交流学习圈:866109386 //帮助1-3年前端人员,提升技术,突破思维 Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中
遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2
遇到 console.log,输出 script end
至此,Event Queue 中存在三个任务,如下表:
看看你掌握了没
再来一个题目,来做个练习:
console.log('script start'); setTimeout(function() { console.log('timeout1'); }, 10); new Promise(resolve=> { console.log('promise1'); resolve(); setTimeout(()=> console.log('timeout2'), 10); }).then(function() { console.log('then1') }) console.log('script end');
这个题目就稍微有点复杂了,我们再分析下:
首先,事件循环从宏任务 (macrotask) 队列开始,最初始,宏任务队列中,只有一个 scrip t(整体代码)任务;当遇到任务源 (task source) 时,则会先分发任务到对应的任务队列中去。所以,就和上面例子类似,首先遇到了console.log,输出 script start; 接着往下走,遇到 setTimeout 任务源,将其分发到任务队列中去,记为 timeout1; 接着遇到 promise,new promise 中的代码立即执行,输出 promise1, 然后执行 resolve ,遇到 setTimeout ,将其分发到任务队列中去,记为 timemout2, 将其 then 分发到微任务队列中去,记为 then1; 接着遇到 console.log 代码,直接输出 script end 接着检查微任务队列,发现有个 then1 微任务,执行,输出then1 再检查微任务队列,发现已经清空,则开始检查宏任务队列,执行 timeout1,输出 timeout1; 接着执行 timeout2,输出 timeout2 至此,所有的都队列都已清空,执行完毕。其输出的顺序依次是:script start, promise1, script end, then1, timeout1, timeout2
用流程图看更清晰:
总结
有个小 tip:从规范来看,microtask 优先于 task 执行,所以如果有需要优先执行的逻辑,放入microtask 队列会比 task 更早的被执行。
最后的最后,记住,JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。。
对前端的技术,架构技术感兴趣的同学关注我的头条号,并在后台私信发送关键字:“前端”即可获取免费的架构师学习资料
知识体系已整理好,欢迎免费领取。还有面试视频分享可以免费获取。关注我,可以获得没有的架构经验哦!!
第一节课的时候,我们学过:
var a=1; //声明变量
if(a%2==0){
console.log(a);
}
a=2;
if(a%2==0){
console.log(a);
}
a=3;
if(a%2==0){
console.log(a);
}
a=4;
if(a%2==0){
console.log(a);
}
。。
a=10
if(a%2==0){
console.log(a);
}
这是打印10以内的偶数。
在这个例子的下面,还有几行语句可以达到相同的效果,但是要精简很多。
for(var i=1;i<=10;i++)
{
if(i%2==0)
{
console.log(i);
}
}
这个for语句就是Javascript的循环语句。
for语句的语法是这样的:
for(初始化语句;条件语句;迭代操作)
{
//语句
}
在上面的例子中,就是:
var i=1 这是初始化语句,声明了一个i的变量,数值是1
i<=10 条件是i小于等于10
i++ 就是每次执行循环,i的值自动加1,进行迭代操作。
下面是例子1:
var count;
for(count=0;count<=10;count=count+1)
{
console.log("Count的值是"+count);
}
另外,还有几种循环语句,这里也一并介绍一下
1、while语句
他的语法是:
while(条件)
{
//执行语句
}
同样是执行打印1到10的值,while是这样的(例子2):
var count=0;
while(count<=10)
{
console.log("Count的值是"+count);
count=count+1; //这个地方是必须的,否则count一直为0,就会进入死循环
}
2、do..while语句
它的语法是:
do {
//执行语句
}while(条件)
同样打印1到10的值(例子3):
var count=0;
do {
console.log("Count的值是"+count);
count=count+1; //这个地方是必须的,否则count一直为0,就会进入死循环
}while(count<=10);
最后一个例子是综合第三节的if语句,来一个稍微复杂的应用。
我们来求一下所有的“水仙花”数。
在百度百科里面,水仙花数的定义是这样的:
水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),
水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身(例如:1^3 + 5^3+ 3^3=153)。
下面是具体的例子(例子4):
var i;
var j;
var k;
for(var i=1;i<=9;i++){
for(j=1;j<9;j++){
for(k=1;k<9;k++){
var real_number=i*100+j*10+k;
if(i*i*i+j*j*j+k*K*k==real_number){
console("发现水仙花数:"+real_number);
}
}
}}
今天就到这里。
avaScript入门教程之循环语句
循环语句从字面意思理解就是重复执行,能够让计算机按照程序员要求重复执行某种操作的能力是所有程序设计语言所必须具备的基本能力。在JavaScript程序设计语言中同样具有循环语句,实现程序代码段的重复执行,本文主要从while语句与for语句两种循环类型方面对循环语句进行说明。
JavaScript为前端程序设计人员提供了三类基本循环控制语句,分别为while语句、do-while 语句与for语句。通过这三种语句程序设计人员可以控制程序重复执行某一操作或者操作的组合。JavaScript基本循环语句执行流程描述如下图所示:
JavaScript基本循环流程图
通过上图我们可知,作为循环控制语句需要提供循环执行的入口、循环体与执行大的出口三部分。其中出口至关重要,出口为结束循环的条件,如果设置不好,程序将一直重复执行,并进入死循环状态。入口我们可以理解为初始条件。出口主要通过判断条件控制,在上图中当判断条件返回值为F时,结束并退出循环。以下我们将分别介绍前文提出的三种类型循环。
在很多程序设计语言基础教程中一般没有把do...while放到整个循环语句最前面进行介绍,主要原因在于与while相比较使用的频率相对较低,而且过多介绍可能让初学者对两者学习记忆产生混淆。此处我们首先介绍do...while 循环,首先我们给出其工作的流程及原理说明。
do...while执行流程
do...while 循环执行过程描述如上图所示,当开始执行循环控制时,首先执行一次循环体,执行完成进行条件判断,成立继续进入循环体执行,否则结束循环。因此可知无论如何都会执行循环体一次。这也是do..while与while语句最大的区别。do...while 语法结构与应用实例如下:
语法说明及示例
基本语法与测试案例如上所示,执行完之后可以得到1-100之间奇数的和,其和为2500。chrome浏览器测试结果如下:
求和计算结果
理解了do...while循环结构之后,再去理解while相对容易,while语句程序执行流程描述如下图所示:
while语句执行流程
while语句执行流程与循环控制基本流程完全一致,首先判断条件,条件成立进入循环体,执行完再判断,直到条件不成立结束循环。while循环语法与示例说明如下:
语法说明及示例
for循环同样可以完成循环流程控制功能,其工作过程与原理基本与while一致,只是其语法相对其他两种较为复杂一点。需要通过三个表达式控制循环,三个表达式分别为初始值、判断表达式、增长值。for循环基本语法与应用示例如下所示:
语法说明及示例
以上针对JavaScript程序设计语言流程控制中的循环操作功能进行了分析与说明,三种控制语句异同也做了解释,并通过示例编写了简单程序进行测试。
本头条号长期关注编程资讯分享;编程课程、素材、代码分享及编程培训。如果您对以上方面有兴趣或代码错误、建议与意见,可以联系作者,共同探讨。更多程序设计相关教程及实例分享,期待大家关注与阅读!JavaScript基础教程系列教程链接如下:
JavaScript基础教程(五)流程控制之条件语句
JavaScript基础教程(四)二进制位运算
JavaScript基础教程(三)64位浮点数加法运算
JavaScript基础教程(二)变量、常量与运算符
*请认真填写需求信息,我们会在24小时内与您取得联系。