先从一个小题目开始: 以下代码的输出结果是?

//
function test1 () {
console.log(1)
}; setTimeout(test1, 1000); // T1-1setTimeout(test1(), 1000); // T1-2
setTimeout(console.log(1.1), 1000); // T1-3

下面还有加强版:

//
function test2(value) {
value = value || 'default 2';
console.log(value);
} setTimeout(test2, 1000, 2.1); // T2-1
setTimeout(test2(), 1000, 2.2); // T2-2
setTimeout(test2(2.3), 1000, 2.31); // T2-3 //
function test3(value) {
value = value || 'default 3';
console.log(value); return test3;
} setTimeout(test3, 1000, 3.1); // T3-1
setTimeout(test3(), 1000, 3.2); // T3-2
setTimeout(test3(3.3), 1000, 3.31) // T3-3 //
for(var i = 0; i < 5; i++) { // T4-1
console.log(i);
} for(var i = 0; i < 5; i++) { // T4-2
setTimeout(function() {
console.log(i);
}, 1000 * i);
} for(var i = 0; i < 5; i++) { // T4-3
setTimeout(function(i) {
console.log(i);
}, 1000 * i);
} for(var i = 0; i < 5; i++) { // T4-4
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
} for(var i = 0; i < 5; i++) { // T4-5
(function() {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
} for(var i = 0; i < 5; i++) { // T4-6
setTimeout((function(i) {
console.log(i);
})(i), i * 1000);
} //
function someLoop(){
var tag = true;
var temp = 0; setTimeout(function(){
tag = !tag;
console.log(tag)
}, 1000); while(tag) {
temp++;
}
} someLoop(); // T5-1

setTimeout 示例2

如果这些对你都是小菜一碟,咳咳,你可以自己回去玩了,这里已经没有啥对你有价值的东西了,我们他日再叙。

如果你对这些代码有些疑惑,那可以坐下来一起探讨一下人生。

先看第一段:

//
function test1 () {
console.log(1)
}; setTimeout(test1, 1000); // T1-1
setTimeout(test1(), 1000); // T1-2
setTimeout(console.log(1.1), 1000); // T1-3

T1-1 这里是最常见的使用方法了, 这个就不用多说了,1s 以后输出 1;

===我是分割线===

T1-2 与 T1-1 的区别就是第一个参数不再是函数,而是函数调用。那是不是1s 以后再调用这个函数呢?在 chrome console 里看一下运行的结果

立即输出了1,又输出了33,1s 以后啥都没有。

这里有3个小问题:1是咋输出的;33是咋输出的;1s 以后啥都没有又是咋回事。

先说33,注意最上边 “// 1” 左边有一个向右的箭头,这代表你在 console 里输入的代码;33左边有一个向左的箭头,这个意思是代码执行后返回的值;33上面两个横线中间的值没有左箭头也没有右箭头,是代码里 console.log() 打印的结果。

这里33代表着 setTimeout() 方法的返回值,返回的是一个计时器,可以用来以后取消定时用的,这个33就是定时器的 id。这里对我们代码的执行并没有啥意义,下面就忽略这个计时器的 id 了。

要想说清楚输出1和1s 以后啥都没有的问题,必须先说说 JavaScript 里的事件循环机制了。

setTimeout(fn, delay) 是在指定时间以后,将 fn 推入到事件循环的消息队列当中去,然后 js 执行引擎跑完执行栈里的代码以后,就立即从这个消息队列里依次取出函数来执行。

这个将 fn 推入消息队列的函数用 js 模拟类似这样:

function pushToMessageQueue(fn){
console.log(fn);
if(fn && Object.prototype.toString.call(fn).toLowerCase() === '[object function]') {
// add fn to Message Queue
// ...... }
} // 我并没有研究过浏览器底层代码实现,如有错误,还请留言指出,谢谢。

执行 setTimeout(fn, delay) 的时候,底层调用这个方法,将 fn 作为参数传递进去。

于是,当我们执行 setTimeout(test1(), 1000) 的时候,底层实际上将 test1() 作为参数传递给 pushToMessageQueue(fn) 函数。

这实际上发生了两件事情:

1,将 test1() 作为参数赋值给 fn;类似于这样:

var fn = test1();

2,运行 pushToMessageQueue 函数逻辑。

在第一步里进行参数传递的时候,实际上是立即执行了 test1(),并将返回值赋值给fn。所以才会立即打印 1。test1 函数并没有返回值,默认返回 undefined,于是 pushToMessageQueue 函数的实际参数是 undefined,于是就没有任何东西被推进执行栈里,所以1s 以后就啥都没有执行。

至此,T1-2的疑问得到解决。了解了这些,再理解接下里的几种情况就容易许多。

===我是分割线===

T1-3 与 T1-2 很像,不过一个是函数调用,一个是语句,但它们运行的过程都是类似的。先把 console.log(1.1) 赋值给 fn,于是立即打印出 1.1,fn 的值为 undefined,于是1s 以后啥都没有执行。

第一段代码输出结果如下:(忽略计时器 id)

===我是分割线===

下面看第二段:

//
function test2(value) {
value = value || 'default 2';
console.log(value);
} setTimeout(test2, 1000, 2.1); // T2-1
setTimeout(test2(), 1000, 2.2); // T2-2
setTimeout(test2(2.3), 1000, 2.31); // T2-3

T2-1 与 T1-1 的区别是多了一个参数 2.2,setTimeout(fn, delay) 常见的写法就是两个参数,一个是要执行的函数,一个是延迟时间,多余的参数什么意思呢?看一下 MDN 上的资料(中文资料在此

var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
var timeoutID = scope.setTimeout(function[, delay]);
var timeoutID = scope.setTimeout(code[, delay]);

这里的 scope 默认是 window。

这家伙有3种用法:

第二种用法,就是最常见的用法;

第三种用法,第一个参数是一个字符串,执行的时候解析为 js 语句再执行。这个用法已经不推荐了,完全可以用第二种代替。

第一种用法,就是在第二种之上加了更多的参数,这些参数会在第一个参数函数 fn 执行的时候作为参数传递,即 fn(param1, param2, param3) 这样。

于是 T2-1 意思就是 1s 以后执行 test2(2.1),结果就是1s 以后打印 2.1。

===我是分割线===

T2-2 与 T1-2 类似,立即执行 test2(),打印 default 2,由于 test2 函数并没有返回值,1s 以后也不会有任何函数被推入消息队列,多余的参数也没有意义。

===我是分割线===

T2-3 里 test2 带了一个参数2.3,立即打印2.3,1s 以后啥都没有。

第二段代码输出结果如下:

===我是分割线===

第三段:

//
function test3(value) {
value = value || 'default 3';
console.log(value); return test3;
} setTimeout(test3, 1000, 3.1); // T3-1
setTimeout(test3(), 1000, 3.2); // T3-2
setTimeout(test3(3.3), 1000, 3.31) // T3-3

T3-1 比较好理解,1s 以后执行 test3,并传入参数 3.1,实际执行的是 test3(3.1),所以1s 以后打印3.1;执行以后返回的 test3 没有被任何变量接收。

===我是分割线===

T3-2 立即执行 test3(),立即打印 default 3,并将返回值 test3 推入消息队列,1s 以后执行 test3(3.2),打印 3.2;

===我是分割线===

T3-3 立即执行 test3(3.3),立即打印 3.3,并将返回值 test3 推入消息队列,1s 以后执行 test3(3.31),打印 3.31;

第三段代码输出结果如下:

===我是分割线===

第四段:

//
for(var i = 0; i < 5; i++) { // T4-1
console.log(i);
} for(var i = 0; i < 5; i++) { // T4-2
setTimeout(function() {
console.log(i);
}, 1000 * i);
} for(var i = 0; i < 5; i++) { // T4-3
setTimeout(function(i) {
console.log(i);
}, 1000 * i);
} for(var i = 0; i < 5; i++) { // T4-4
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
} for(var i = 0; i < 5; i++) { // T4-5
(function() {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
} for(var i = 0; i < 5; i++) { // T4-6
setTimeout((function(i) {
console.log(i);
})(i), i * 1000);
}

T4-1:

输出 0 1 2 3 4

===我是分界线===

T4-2:

前面说到,setTimeout(fn, delay) 是在 delay 后将 fn 推入消息队列,等主进程执行完执行栈中的代码以后再执行执行消息队列里的回调。这里 for 循环就是执行栈中的代码,setTimeout 第一个参数里的回调都在指定时机被推入消息队列,循环完毕以后再执行。而此时的 i 已经变成了5,因此,打印结果是,立即打印一个5,然后接下来每秒再打印一个5。一共5个5。

5 (1s 后)5(2s 后)5 (3s 后)5 (4s 后)5

===我是分界线===

T4-3:

和上一个的区别是,回调里多了一个 i 参数。但在执行的时候并没有实参传递给这个回调,因此 i 就是 undefined,输出结果跟上一个类似,只是把 5 换成了 undefined。

undefined (1s 后)undefined(2s 后)undefined (3s 后)undefined (4s 后)undefined

===我是分界线===

T4-4:

循环体内部变成了一个立即执行函数,并把 i 作为参数传递。于是就利用闭包保存了对每一个 i 的引用。

0 (1s 后)1(2s 后)2 (3s 后)3 (4s 后)4

===我是分界线===

T4-5

与上一个的区别是,循环体内的立即执行函数没有形参,于是传递的参数 i 就没有参数来接收,于是匿名函数内部的 i 引用的其实都是外面循环里的 i。本质上和 T4-2并没有区别,输出也是一样的。

5 (1s 后)5(2s 后)5 (3s 后)5 (4s 后)5

===我是分界线===

T4-6

setTimeout 的第一个参数变成了一个立即执行函数,这和 T1-2 是类似的,只不过外部多了一个循环而已。

0 1 2 3 4 (1s 后)无输出

第四段 输出如下

===我是分界线===

第五段

//
function someLoop(){
var tag = true;
var temp = 0; setTimeout(function(){
tag = !tag;
console.log(tag)
}, 1000); while(tag) {
temp++;
}
} someLoop(); // T5-1

根据上面的分析,这个应该很好理解。这段代码是个死循环,永远不会输出。

结果:

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

setTimeout 与 Event Loop 浅析的更多相关文章

  1. 浅析Node.js的Event Loop

    目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...

  2. 以setTimeout来聊聊Event Loop

    平时的工作中,也许你会经常用到setTimeout这个方法,可是你真的了解setTimeout吗?本文想通过总结setTimeout的用法,顺便来探索javascript里面的事件执行机制. setT ...

  3. 定时器setTimeout()和Node.js的Event Loop

    一.定时器 setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行.它在"任务队列"的尾部添加一个事件,因此要等到同步任务和 ...

  4. 【原】以setTimeout来聊聊Event Loop

    平时的工作中,也许你会经常用到setTimeout这个方法,可是你真的了解setTimeout吗?本文想通过总结setTimeout的用法,顺便来探索javascript里面的事件执行机制. setT ...

  5. setTimeout 的黑魔法 【event loop】

    setTimeout,前端工程师必定会打交道的一个函数.它看上去非常的简单,朴实.有着一个很不平凡的名字--定时器.让年少的我天真的以为自己可以操纵未来.却不知朴实之中隐含着惊天大密.我还记得我第一次 ...

  6. setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop

    笔者以前面试的时候经常遇到写一堆setTimeout,setImmediate来问哪个先执行.本文主要就是来讲这个问题的,但是不是简单的讲讲哪个先,哪个后.笼统的知道setImmediate比setT ...

  7. javascript运行模式:并发模型 与Event Loop

    看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和[朴灵评注]的文章,查阅网上相关资料,把自己对javascript运行模式和EVENT loop的理解整理下,不一定对,日 ...

  8. JavaScript 运行机制详解:再谈Event Loop

    原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...

  9. 数据密集型 和 cpu密集型 event loop

    Node.js在官网上是这样定义的:“一个搭建在Chrome JavaScript运行时上的平台,用于构建高速.可伸缩的网络程序.Node.js采用的事件驱动.非阻塞I/O模型使它既轻量又高效,是构建 ...

随机推荐

  1. WCF学习——构建第二个WCF应用程序(五)

    一.创建数据服务 1.在“解决方案资源管理器”中,使用鼠标左键选中“WcfService”项目,然后在菜单栏上,依次选择“项目”.“添加新项”. 2.在“添加新项”对话框中,选择“Web”节点,然后选 ...

  2. python迭代器生成器(三)

    扩展的列表解析语法 今天接着昨天的继续写. 列表解析可以变得更加复杂---例如,它可以包含嵌套的循环,也可能被编写为一系列的for子句.(这里只是简单介绍一下,以后再说这个语法的问题) 例如:构建一个 ...

  3. Your password does not satisfy the current policy requirements

    创建用户,做测试想设置一个简单的密码.报错: 大概是MySQL5.7搞事情,默认安装了validate_password插件. mysql> SHOW VARIABLES LIKE 'valid ...

  4. eclipse 修改中英文显示

    1.关闭eclipse 2.右键->选择属性--->打开文件位置 3.找到“eclipse.ini”文件,用EditPlus打开 4.在里面加 “-Duser.language=en”,即 ...

  5. JavaScript一个google地图获取

    <script type="text/javascript"> /** * 返回一个新创建的<img>元素,该元素用于在获取到地理位置信息后,显示一张Goo ...

  6. 由max_allowed_packet引发的mysql攻防大战

    1.原因 程序的sql语句比较长.max_allowed_packet默认是1024.于是就报错了.一开始手动改 global max_allowed_packet ,改完后.莫名奇妙被还原.后来改配 ...

  7. USB的四种传输类型与端点

    1.事务 在介绍USB传输类型之前,请允许我先简答介绍一下USB事务. 事务一般由令牌包.数据包(可选).握手包组成. 令牌包:用来启动一个事务,总是由主机发送. 数据包:可以从主机到设备,也可以由设 ...

  8. LeetCode-Maximum Subarray[dp]

    Maximum Subarray Find the contiguous subarray within an array (containing at least one number) which ...

  9. 【Owin 学习系列】2. Owin Startup 类解析

    Owin Startup 类解析 每个 Owin 程序都有 startup 类,在这个 startup 类里面你可以指定应用程序管道模型中的组件.你可以通过不同的方式来连接你的 startup 类和运 ...

  10. ARKit 初体验

    ARKIT是苹果公司在今年发布的一个AR开发包,用于现有的IOS设备,是的,就是用在手机或者平板上,类似于pokemon go的效果.看了下演示视屏,嗯,看起来很厉害. 对于一个资深软粉,居然被要求研 ...