你不知道的Javascript:有趣的setTimeout

有时候,小小的细节往往隐藏着大大的智慧
今天在回顾JavaScript进阶用法的时候,发现一个有趣的问题,话不多说,先上代码:

for(var j=0;j<10;j++){
setTimeout(function(){console.log(j)},5000)
}

看到这三行代码,也许你会不耐烦道:又要讲闭包?要吐了好么?别急,让我们先来思考一下,这段代码在浏览器中的执行结果是什么?

甲:顺序打印0到9?

乙:这题我见过,打印十个10!

哪个答案正确?我们继续上图:

执行结果显示,浏览器打印出了十个10(因为图片处理的原因,按下回车到打印之前其实间隔了5秒左右),貌似乙胜出了。但如果你足够细心,你会发现几个问题:

  1. 为什么会循环打印十个10而不是0到9?
  2. 从结果来看,for循环执行完跳出之后,才开始执行setTimeout(所以j才等于10),为什么不是每次迭代都执行一次setTimeout呢?

如果上述三个问题你都能回答上来,恭喜你,你已经开始掌握了JavaScript深层次的知识,如果不能,那就乖乖往下看吧!

为什么会循环打印十个10

许多人习惯用第二个问题中的执行结果来回答这个问题:“for循环执行完跳出之后,才开始执行setTimeout,所以才打印了十个10”。这样的答案,只能说是既应付了自己,又应付了别人。其实,要解答第一个问题,首先要解答的就是第二的问题。

为什么不是每次迭代都执行一次setTimeout

大家都知道,JavaScript在ES6出现以前,是没有块状作用域的,这就意味着, 在for循环中用var定义的变量j,其实是属于全局的,即在全局范围内都可以被访问到,既然如此,那其实整个全局作用域中就只有一个j,每次for循环都i是在更新这个j。

那么现在关键的问题在于,为什么整个for循环会先于setTimeout执行,而不是我们正常理解的,一次迭代执行一次。

这就涉及到了JavaScript的核心特性:单线程。

JavaScript设计的初衷,是浏览器用来与用户进行交互和DOM操作的。这就决定了它必须是单线程的,设想JavaScript同事有两个线程,一个线程在DOM节点添加内容,一个线程删除该节点,浏览器就会出现混乱。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

为了优化单线程的性能,JavaScript将任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有主线程中的同步任务执行完毕,异步任务才会进入执行队列执行。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

而setTimeout,就被JavaScript定义为异步任务。每次for循环的迭代,都将setTimeout中的回调函数加入任务队列等待执行。也就是说,只有同步任务中的for循环完全结束,主线程中才会去任务队列中找到尚未执行的十个setTimeout(十次迭代)回调函数并顺序执行(先进先出)。而此时,i已经经过循环结束变成了10,所以,此时主线程执行的,是十个一摸一样的打印i的回调函数,即打印十个10。至此就完美回答了第一和第二个问题,文章开头的代码与下面的代码其实是等价的:

for(var i=0;i<10;i++){}
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)

小小的一个setTimeout,牵扯出了很多JavaScript的深层次问题,虽然总结成一篇文章只有区区数百字,但是我在成文的过程中查阅了大量的资料,也做了许多实验。

最后,给出一个很小但是仍然在困扰我的一个问题,希望有兴趣的小伙伴可以跟我一起研究:

setTimeout(function(){while(true){}},6000);
setTimeout(function(){console.log(1)},10000);
setTimeout(function(){console.log(2)},5000);

上述代码的执行顺序是怎样的?setTimeout的定时,是定时插入执行栈之后立即执行,还是立即插入执行栈定时执行?

期待大家的留言。

你不知道的Javascript:有趣的setTimeout的更多相关文章

  1. 《你不知道的javascript》读书笔记1

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...

  2. 《你不知道的JavaScript》整理(二)——this

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,这次研究了一下“this”. 当一个函数被调用时,会创建一个活动记录(执行上下文). 这个记录会包含函 ...

  3. 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...

  4. 你不知道的JavaScript上卷笔记

    你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章   初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目 ...

  5. 【读书笔记】-- 你不知道的JavaScript

    <你不知道的JavaScript>是一个不错的JavaScript系列书,书名可能有些标题党的意思,但实符其名,很多地方会让你有耳目一新的感觉. 1.typeof null === &qu ...

  6. 读书笔记-你不知道的JavaScript(上)

    本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...

  7. 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)

    github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...

  8. JavaScript中的this(你不知道的JavaScript)

    JavaScript中的this,刚接触JavaScript时大家都在大肆渲染说其多么多么的灵巧重要,然而自己并不关心:随着自己对JavaScript一步步深入了解,突然恍然大悟,原来它真的很重要!所 ...

  9. 《你不知道的 JavaScript 上卷》 学习笔记

    第一部分: 作用域和闭包 一.作用域 1. 作用域:存储变量并且查找变量的规则 2. 源代码在执行之前(编译)会经历三个步骤: 分词/此法分析:将代码字符串分解成有意义的代码块(词法单元) 解析/语法 ...

随机推荐

  1. Qt实现QQ界面

    1.Qt实现QQ界面是通过QToolBox类来实现的,基本结构是:QToolBox里面装QGroupBox,然后QGroupBox里面装QToolButton,设置好相关属性即可 2.定义类继承QTo ...

  2. 前端MVC Vue2学习总结(七)——ES6与Module模块化、Vue-cli脚手架搭建、开发、发布项目与综合示例

    使用vue-cli可以规范项目,提高开发效率,但是使用vue-cli时需要一些ECMAScript6的知识,特别是ES6中的模块管理内容,本章先介绍ES6中的基础与模块化的内容再使用vue-cli开发 ...

  3. JQuery 网页瞄点

    $("html,body").animate({ scrollTop: $("#Content1").offset().top }, 3000); 代码说明:h ...

  4. Python pandas ERROR 2006 (HY000): MySQL server has gone away

    之前在做python pandas大数据分析的时候,在将分析后的数据存入mysql的时候报ERROR 2006 (HY000): MySQL server has gone away 原因分析:在对百 ...

  5. SQL Server 插入含有中文字符串出现乱码现象的解决办法

    ELECT  COLLATIONPROPERTY('Chinese_PRC_Stroke_CI_AI_KS_WS', 'CodePage')       --查询SQLServer编码格式的语句 下面 ...

  6. FreeMarker解析json数据

    模拟后台传过来的 json 字符串text <#assign text> {"res":"0","msg":"OK&q ...

  7. [转]"git rm" 和 "rm" 的区别

    用 git rm 来删除文件,同时还会将这个删除操作记录下来 直观的来讲,git rm 删除过的文件,执行 git commit -m "abc" 提交时, 会自动将删除该文件的操 ...

  8. [译]Python面试中8个必考问题

    1.下面这段代码的输出结果是什么?请解释. def extendList(val, list=[]): list.append(val) return list list1 = extendList( ...

  9. VMware 设置共享目录

    VMware 共享目录设置 1,选择"虚拟机"->"重新安装VMware Tools"2,挂载cdrom3,拷贝VMware-tool.tar.gz 到L ...

  10. Flask笔记

    # -*- coding: utf-8 -*- from flask import Flask,render_template,request,redirect,session,url_for imp ...