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

//
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. iOS基于AVPlayer的视频播放

    基于 AVPlayer 自定义播放器http://www.cocoachina.com/ios/20160921/17609.html,http://www.2cto.com/kf/201608/53 ...

  2. Windows7下pip源配置修改

    以下列举三种方式的pip源配置: 1. 设置环境变量PIP_CONFIG_FILE指向pip.ini源配置文件,pip.ini文件内容如下: [global] index-url = http://m ...

  3. Thrift总结(二)创建RPC服务

    前面介绍了thrift 基础的东西,怎么写thrift 语法规范编写脚本,如何生成相关的语言的接口.不清楚的可以看这个<Thrift总结(一)介绍>.做好之前的准备工作以后,下面就开始如何 ...

  4. 【summary】JQuery 相关css、ajax、数据操作函数或方法

    总结一下JQuery常用的函数方法,更加系统的整理一下. JQuery遍历的一些函数: 函数 描述 .add() 将元素添加到匹配元素的集合中. .andSelf() 把堆栈中之前的元素集添加到当前集 ...

  5. Bootstrap提示信息(标签、徽章、巨幕和页头)

    前面的话 在Bootstrap中,有一些组件用于提示信息,如 标签.徽章.巨幕和页头.本文将详细介绍Bootstrap提示信息 标签 在一些Web页面中常常会添加一个标签用来告诉用户一些额外的信息,比 ...

  6. 浙江省新高中信息技术教材,将围绕Python进行并增加编程相关知识点

    2017年初消息: 浙江省信息技术新教材,即将在2017级(2017年9月入学)高中新生中开始使用. 据了解,与目前的选考(可以理解为高考科目)要求的信息技术教材由3本<信息技术基础>.& ...

  7. Java面试常考------------------------垃圾收集算法

    对于Java系学生而言,Java虚拟机中的垃圾收集算法是一个很重要的面试考点. 常用的垃圾收集算法主要可划分为以下三类: 1. 标记-清除算法 标记清除算法是一种比较简单的方法,直接标记内存中待回收的 ...

  8. Linux学习笔记(二)——文件/目录/VIM

    文件和目录管理 及 VI编辑器的使用 文件和目录管理,刚开始学这块的时候感觉内容很多很杂,但是学完进行总结后,发现其实很有条理的而且没什么难度,只是熟练掌握这些常用的命令就行了.至于Vim编辑器,不得 ...

  9. # .NET切面编程——PostSharp

    目录 概念 实现方式 .Net平台的切面实现 PostSharp示例 概念 Aspect-Oriented Programming(AOP):想想OOP是不是有些熟悉,AOP翻译过来的意思就是面向切面 ...

  10. git - 远程分支

    对于用户来说,git给人提交到本地的机会.我们可以在自己的机器上创建不同的branch,来测试和存放不同的代码. 对于代码管理员而言,git有许多优良的特性.管理着不同的分支,同一套源代码可以出不一样 ...