从setTimeout看js函数执行过程


Posted in Javascript onDecember 19, 2017

老实说,写这篇文章的时候心里是有点压抑的,因为受到打击了,为什么?就 因为喜欢折腾不小心看到了这个"简单"的函数:

for (var i = 0; i < 5; i++) {
      setTimeout(function () {
        console.log(i)
      }, i * 1000);
    }
    console.log(i);

什么?这不就是我很久之前看到的先打印一个5,再打印一个5,之后每隔一秒就打印一个5,直到打印完6个5的实现方法吗?那么问题来了,如果我要依次打印0,1,2,3,4,5的话我该怎么办,其实在这之前我就知道有这两个方法:一个是这样:

function log(i){
setTimeout(function(){
console.log(i)
},i*1000)
};
for (var i = 0; i < 5; i++) {
      log(i) ;
    }
    console.log(i);

   还有一个是这样:

for(var i=0;i<5;i++){
(function(e){
setTimeout(function(){
console.log(e)
},i*1000);
})(i);
};
console.log(i);

不怕笑话,在这之前我是没搞懂这两个函数真正意义上的作用是用来干嘛的,只强迫自己这样记住这样修改就可以了,但是现在不行啊,我有强迫症啊!于是,我慢慢分析了一下,发现上面那段代码可以分离成这样:

i=0时;满足条件;

setTimeout(function(){
console.log(i)
},0*1000);

i=1时;满足条件;

setTimeout(function(){
console.log(i)
},1*1000);

i=2时;满足条件;

setTimeout(function(){
console.log(i)
},2*1000);

i=3时;满足条件;

setTimeout(function(){
console.log(i)
},3*1000);

i=4时;满足条件;

setTimeout(function(){
console.log(i)
},4*1000);

i=5时,不满足条件,跳出循环,接着执行for循环后面的console.log(i),打印5;最后依次每秒打印5;

真有意思,为什么setTimeout里面的console.log会是后于for循环外面的console.log执行呢?直到我认识到了这个单词=>"队列", 队列又有宏任务队列(Macro Task)以及微任务队列(Micro Task)之分 ,在javascript中:

macro-task包括:script(整体代码), setTimeout , setInterval, setImmediate, I/O, UI rendering。

micro-task包括:process.nextTick, Promises , Object.observe, MutationObserver

上面函数的setTimeout就属于宏任务

在js中,事件循环的顺序是从script开始第一次循环,随后全局上下文进入函数调用栈,碰到macro-task就将其交给处理它的模块处理完之后将回调函数放进macro-task的队列之中,碰到micro-task也是将其回调函数放进micro-task的队列之中。直到函数调用栈清空只剩全局执行上下文,然后开始执行所有的micro-task。 当所有可执行的micro-task执行完毕之后。循环再次执行macro-task中的一个任务队列 ,执行完之后再执行所有的micro-task,就这样一直循环。

这就是为什么setTimeout里面的console.log会是后于for循环外面的console.log执行,在函数执行上下文中,seiTimeout函数会被放到处理他的macro-task的队列之中,所以循环的时候setTimeout里面的function是不会被执行的,而是等到所有整体代码(非队列)跑完之后才会执行队列中的函数;写到这里,可能会有点懵逼,其实我也有点懵逼,哈哈哈!!

为了加深理解,还可以试试在里面加入Promise,于是就有了这个:

(function copy() {
  setTimeout(function() {console.log(4)}, 0);
  new Promise(function executor(resolve) {
    console.log(1);
    for( var i=0 ; i<10000 ; i++ ) {
      i == 9999 && resolve();
    }
    console.log(2);
  }).then(function() {
    console.log(5);
  });
  console.log(3);
})()

解释一下=>

1.首先,script任务源先执行,全局上下文入栈。

2.script任务源的代码在执行时遇到setTimeout,作为一个macro-task,将其回调函数放入自己的队列之中。

3.script任务源的代码在执行时遇到Promise实例。Promise构造函数中的第一个参数是在当前任务直接执行不会被放入队列之中,因此此时输出 1 。

4.在for循环里面遇到resolve函数,函数入栈执行之后出栈,此时Promise的状态变成Fulfilled。代码接着执行遇到console.log(2),输出2。

5.接着执行,代码遇到then方法,其回调函数作为micro-task入栈,进入Promise的任务队列之中,此时Promise的then 里面的function回调函数跟setTimeout里面的function回调函数有着异曲同工之意,都会被放到各自的任务队列中,

 直到函数上下文即script中所有的非队列代码执行完毕后再执行,而且微任务队列优先于宏任务队列被处理,

  总体顺序为:上下文非队列代码>微任务队列回调函数代码>宏任务队列回调函数代码

6.代码接着执行,此时遇到console.log(3),输出3。

7.输出3之后第一个宏任务script的代码执行完毕,这时候开始开始执行所有在队列之中的micro-task。then的回调函数入栈执行完毕之后出栈,这时候输出5

8.这时候所有的micro-task执行完毕,第一轮循环结束。第二轮循环从setTimeout的任务队列开始,setTimeout的回调函数入栈执行完毕之后出栈,此时输出4。

最后,为了加深理解,再上一段代码:

console.log('golb1');
setTimeout(function() {
  console.log('timeout1');
  new Promise(function(resolve) {
    console.log('timeout1_promise');
    resolve();

setTimeout(function(){



console.log('time_timeout')


});

  }).then(function() {
    console.log('timeout1_then')
  })
  setTimeout(function() {
   console.log('timeout1_timeout1');
  });
})
new Promise(function(resolve) {
  console.log('glob1_promise');
  resolve();

setTimeout(function(){


 console.log('prp_timeout')


});
}).then(function() { console.log('glob1_then') })

如果你的执行结果是:golb1=>glob1_promise=>glob1_then=>timeout1=>timeout1_promise=>timeout1_then=>prp_timeout=>time_timeout=>timeout1_timeout1,

可能异步队列算是入门了吧!~~上面的代码看起来有点杂乱,可能用asyns搭配await改造一下会更好,但是这或多或少是鄙人从setTimeout中得到的见解吧

总结

以上所述是小编给大家介绍的从setTimeout看js函数执行过程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript中两个字符串的匹配
Jun 08 Javascript
手机端图片缩放旋转全屏查看PhotoSwipe.js插件实现
Aug 25 Javascript
js弹性势能动画之抛物线运动实例详解
Jul 27 Javascript
原生JavaScript实现Ajax异步请求
Nov 19 Javascript
解决JS表单验证只有第一个IF起作用的问题
Dec 04 Javascript
在微信小程序中使用图表的方法示例
Apr 25 Javascript
Node.js 获取微信JS-SDK CONFIG的方法示例
May 21 Javascript
浅谈Vue组件单元测试究竟测试什么
Feb 05 Javascript
javascript执行上下文、变量对象实例分析
Apr 25 Javascript
原生js实现五子棋游戏
May 28 Javascript
解决vue bus.$emit触发第一次$on监听不到问题
Jul 28 Javascript
ztree+ajax实现文件树下载功能
May 18 Javascript
mongoose更新对象的两种方法示例比较
Dec 19 #Javascript
jquery中done和then的区别(详解)
Dec 19 #jQuery
JavaScript实现AOP详解(面向切面编程,装饰者模式)
Dec 19 #Javascript
利用nginx + node在阿里云部署https的步骤详解
Dec 19 #Javascript
使用Vue自定义数字键盘组件(体验度极好)
Dec 19 #Javascript
vue 通过下拉框组件学习vue中的父子通讯
Dec 19 #Javascript
浅谈Vue.js中ref ($refs)用法举例总结
Dec 19 #Javascript
You might like
php str_replace的替换漏洞
2008/03/15 PHP
PHP中的密码加密的解决方案总结
2016/10/26 PHP
基于MooTools的很有创意的滚动条时钟动画
2010/11/14 Javascript
两个Javascript小tip资料
2010/11/23 Javascript
JavaScript 注册事件代码
2011/01/27 Javascript
js自动下载文件到本地的实现代码
2013/04/28 Javascript
Jquery和JS用外部变量获取Ajax返回的参数值的方法实例(超简单)
2013/06/17 Javascript
如何从jQuery的ajax请求中删除X-Requested-With
2013/12/11 Javascript
js+CSS实现弹出居中背景半透明div层的方法
2015/02/26 Javascript
通过实例理解javascript中没有函数重载的概念
2015/06/03 Javascript
MVVM模式中ViewModel和View、Model有什么区别?
2015/06/19 Javascript
JS获取鼠标坐标位置实例分析
2016/01/20 Javascript
Hallo.js基于jQuery UI所见即所得的Web编辑器
2016/01/26 Javascript
jquery获取点击控件的绝对位置简单实例
2016/10/13 Javascript
Vue.js 和 MVVM 的注意事项
2016/11/07 Javascript
jQuery实现移动端Tab选项卡效果
2017/03/15 Javascript
Vue.js学习笔记之常用模板语法详解
2017/07/25 Javascript
JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法
2020/04/13 Javascript
[01:03]悬念揭晓 11月26日DOTA2完美盛典不见不散
2017/11/23 DOTA
python的Template使用指南
2014/09/11 Python
深入理解Python中的元类(metaclass)
2015/02/14 Python
Python运行报错UnicodeDecodeError的解决方法
2016/06/07 Python
Python中第三方库Requests库的高级用法详解
2017/03/12 Python
python:pandas合并csv文件的方法(图书数据集成)
2018/04/12 Python
python实现搜索文本文件内容脚本
2018/06/22 Python
Django 创建新App及其常用命令的实现方法
2019/08/04 Python
selenium携带cookies模拟登陆CSDN的实现
2021/01/19 Python
精致的手工皮鞋:Shoe Embassy
2019/11/08 全球购物
某公司Java工程师面试题笔试题
2016/03/27 面试题
Prototype是怎么扩展DOM的
2014/10/01 面试题
《颐和园》教学反思
2014/02/26 职场文书
技术岗位竞聘演讲稿
2014/05/16 职场文书
小学美术兴趣小组活动总结
2014/07/07 职场文书
二年级学生期末评语
2014/12/26 职场文书
幼儿园大班开学寄语(2015秋季)
2015/05/27 职场文书
python实现股票历史数据可视化分析案例
2021/06/10 Python