JavaScript中setTimeout的那些事儿


Posted in Javascript onNovember 14, 2016

一、setTimeout那些事儿之单线程 

一直以来,大家都在说Javascript是单线程,浏览器无论在什么时候,都且只有一个线程在运行JavaScript程序。 

但是,不知道大家有疑问没——就是我们在编程过程中的setTimeout(类似的还有setInterval、Ajax),不是异步执行的吗?!! 

例如:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 </head>
 <body>
  <script>
   console.log("a");
   //利用setTimeout延迟执行匿名函数
   setTimeout(function(){
    console.log("b");
   },100);
   console.log("c");
  </script>
 </body>
</html>

运行代码,打开chrome调试器,得如下结果

 JavaScript中setTimeout的那些事儿

这个结果很容易理解,因为我setTimeout里的内容是在100ms后执行的嘛,当然是先输出a,再输出c,100ms后再输出setTimeout里的b嘛。 

咦,那Javascript这不就不是单线程了嘛,这不就可以实现多线程了?!! 

其实,不是的。setTimeout没有打破JavaScript的单线程机制,它其实还是单线程。 

为什么这么说呢,那就得理解setTimeout到底是个怎么回事。 

请看下面的代码,猜结果:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 </head>
 <body>
  <script>
   var date = new Date();
   //打印才进入时的时间
   console.log('first time: ' + date.getTime());
   //一秒后打印setTimeout里匿名函数的时间
   setTimeout(function(){
    var date1 = new Date();
    console.log('second time: ' + date1.getTime() );
    console.log( date1.getTime() - date.getTime() );
   },1000);
   //重复操作
   for(var i=0; i < 10000 ; i++){
    console.log(1);
   }
  </script>
 </body>
</html>

看了上面的代码,猜猜输出的结果是多少呢?1000毫秒? 

我们打开chrome调试器,见下图

JavaScript中setTimeout的那些事儿 

纳尼,怎么不是1000毫秒呢?!!! 

我们再看看下面的代码:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 </head>
 <body>
  <script>
   //一秒后执行setTimeout里的匿名函数,alert下
   setTimeout(function(){
    alert("monkey");
   },1000);
   while(true){};
  </script>
 </body>
</html>

运行代码后!

怎么一直刷新,浏览器卡死了呢,并且没有alert!! 

按道理,即使我while无限循环,在1秒后也得alert一下啊。 

种种问题皆一个原因,JavaScript是单线程 。 

要记住JavaScript是单线程,setTimeout没有实现多线程,它背后的真相是这样滴: 

JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。 

浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程。 

*JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。 

*GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。 

*事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。 

so,通过以上讲解,以上种种问题迎刃而解。 

二、setTimeout那些事儿之延迟时间为0 

当setTimeout的延迟时间为0时,大家想想它会怎么执行呢? 

例如下面的代码:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 </head>
 <body>
  <script>
   console.log('a');
   setTimeout(function(){
    console.log('b');
   },0);
   console.log('c');
   console.log('d');
  </script>
 </body>
</html>

运行代码结果如下:

JavaScript中setTimeout的那些事儿 

假设你已经知道Javascript单线程的运行原理了。那么,可能会有这样的疑问:setTimeout的时间为0,都没加到处理队列的末尾,怎么会晚执行呢?不应该立即执行吗? 

我的理解是,就算setTimeout的时间为0,但是它仍然是setTimeout啊,原理是不变的。所以会将其加入到队列末尾,0秒后执行。 

况且,经过查找资料发现,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动。

 这个最小的时间间隔是多少呢? 

这和浏览器及操作系统有关。在John Resig的《Javascript忍者的秘密》一书中提到?Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒),另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。 

说了这么多,setTimeout的延迟时间为0,看来没什么意义嘛,都是放在队列后执行嘛。 

非也,天生我材必有用,就看你怎么用咯。抛砖迎玉下。 

1、可以用setTimeout的延迟时间为0,模拟动画效果哦。 

详情请见下代码:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 </head>
 <body>
  <div id="container" style="width:100px;height:100px;border:1px solid black;"></div>
  <div id="btn" style="width:40px;height:40px;line-height:40px;margin-top:20px;background:pink;">click</div>
  <script>
   window.onload = function(){
    var con = document.getElementById('container');
    var btn = document.getElementById('btn'); 
    //Params: i 为起始高度,num为预期高度
    function render(i, num) {
     i++; 
     con.style.height = i + 'px';
     //亮点在此
     if(i < num){
      setTimeout(function() {
       render(i, num);
      },0);
     }
     else {
      con = null;
      btn = null;
     }
    };
    btn.onclick = function(){
     render(100, 200);
    };
   };
  </script>
 </body>
</html>

由于是动画,所以想看其效果,还请各位看官运行下代码哦。 

代码第19行中,利用setTimeout,在每一次render执行完成(给高度递增1)后,由于Javascript是单线程,且setTimeout里的匿名函数会在render执行完成后,再执行render。所以可以实现动画效果。 

2、可以用setTimeout的延迟时间为0,实现捕获事件哦。 

当我们点击子元素时,我们可以利用setTimeout的特性,来模拟捕获事件。 

请见如下代码:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <style> 
   #parent {
    width:100px;
    height:100px;
    border:1px solid black;
   } 
   #child {
    width:50px;
    height:50px;
    background:pink;
   }
  </style>
 </head>
 <body>
  <div id="parent">
   <div id="child"></div>
  </div>
  <script>
   //点击子元素,实现子元素的事件在父元素触发后触发
   window.onload = function(){
    var parent = document.getElementById('parent'); 
    var child = document.getElementById('child');
    parent.onclick = function(){
     console.log('parent');
    }
    child.onclick = function(){
     //利用setTimeout,冒泡结束后,最后输出child
     setTimeout(function(){
      console.log('child'); 
     },0);
    }
    parent = null;
    child = null; 
   }
  </script>
 </body>
</html>

 执行代码,点击粉红色方块,输出结果: 

JavaScript中setTimeout的那些事儿

三、setTimeout那些事儿之this 

说到this,对于它的理解就是:this是指向函数执行时的当前对象,倘若没有明确的当前对象,它就是指向window的。 

好了,那么我们来看看下面这段代码:

<!DOCTYPE html>
 <head>
  <title>setTimeout</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 </head>
 <body>
  <script>
   var name = '!!';
   var obj = {
    name:'monkey',
    print:function(){
     console.log(this.name);
    },
    test:function(){
     //this.print
     setTimeout(this.print,1000);
    }
   }
   obj.test();
  </script>
 </body>
</html>

通过chrome调试器,查看输出结果: 

JavaScript中setTimeout的那些事儿

咦,它怎么输出的是”!!”呢?不应该是obj里的”monkey”吗?!! 

这是因为setTimeout中所执行函数中的this,永远指向window。 

不对吧,那上面代码中的setTimeout(this.print,1000)里的this.print怎么指向的是obj呢?!! 

注意哦,我这里说的是“延迟执行函数中的this”,而不是setTimeout调用环境下的this。 

什么意思? 

setTimeout(this.print,1000),这里的this.print中的this就是调用环境下的; 

而this.print=function(){console.log(this.name);},这个匿名函数就是setTimeout延迟执行函数,其中的this.name也就是延迟执行函数中的this啦。 

嘿嘿,这下明白了吧。

var age = 24;
function Fn(){
 this.age = 18;
 setTimeout(function(){
  //this代表window
  console.log(this);
  //输出24,而不是Fn的18
  console.log(this.age);
 },1000);
}
new Fn();

咦,那有个疑问,比如我想在setTimeout延迟执行函数中的this指向调用的函数呢,而不是window?!!我们该怎么办呢。 

常用的方法就是利用that。 

that? 

对,that。利用闭包的知识,让that保证你传进去的this,是你想要的。 

详情见下:

var age = 24;
function Fn(){
 //that在此
 var that = this;
 this.age = 18;
 setTimeout(function(){
  console.log(that);
  console.log(that.age);
 },1000);
}
new Fn();

还有一种方法就是,利用bind。 

如下:

var age = 24;
function Fn(){
 this.age = 18;
 //bind传入this
 setTimeout(function(){
  console.log(this);
  console.log(this.age);
 }.bind(this),1000);
}
new Fn();

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 去字符串空格终极版(支持utf8)
Nov 14 Javascript
Uglifyjs(JS代码优化工具)入门 安装使用
Apr 13 Javascript
Javascript中的作用域和上下文深入理解
Jul 03 Javascript
通过Jquery.cookie.js实现展示浏览网页的历史记录超管用
Oct 23 Javascript
javascript简单判断输入内容是否合法的方法
May 11 Javascript
详解Angular开发中的登陆与身份验证
Jul 27 Javascript
Javascript实现倒计时(防页面刷新)实例
Dec 13 Javascript
微信小程序选择图片和放大预览图片功能
Nov 02 Javascript
js+css实现打字效果
Jun 24 Javascript
Auto.js自动收取自己和好友蚂蚁森林能量脚本
Jun 28 Javascript
微信小程序实现自上而下字幕滚动
Jul 14 Javascript
nuxt 实现在其它js文件中使用store的方式
Nov 05 Javascript
jquery css实现邮箱自动补全
Nov 14 #Javascript
JS常用算法实现代码
Nov 14 #Javascript
node.js缺少mysql模块运行报错的解决方法
Nov 13 #Javascript
JavaScript判断浏览器对CSS3属性是否支持的多种方法
Nov 13 #Javascript
JS实现的几个常用算法
Nov 12 #Javascript
AngularJS操作键值对象类似java的hashmap(填坑小结)
Nov 12 #Javascript
使用纯JS代码判断字符串中有多少汉字的实现方法(超简单实用)
Nov 12 #Javascript
You might like
PHP MSSQL 存储过程的方法
2008/12/24 PHP
CI框架入门示例之数据库取数据完整实现方法
2014/11/05 PHP
详解PHP字符串替换str_replace()函数四种用法
2017/10/13 PHP
thinkPHP框架自动填充原理与用法分析
2018/04/03 PHP
laravel实现图片上传预览,及编辑时可更换图片,并实时变化的例子
2019/11/14 PHP
javascript String 的扩展方法集合
2008/06/01 Javascript
基于JQuery实现CheckBox全选全不选
2011/06/27 Javascript
中文字符串截取的js函数代码
2013/04/17 Javascript
Extjs 点击复选框在表格中增加相关信息行
2016/07/12 Javascript
vue.js+boostrap项目实践(案例详解)
2016/09/21 Javascript
jQuery插件echarts实现的单折线图效果示例【附demo源码下载】
2017/03/04 Javascript
浅谈js-FCC算法Friendly Date Ranges(详解)
2017/04/10 Javascript
详解nodeJS之路径PATH模块
2017/05/31 NodeJs
vue2.0s中eventBus实现兄弟组件通信的示例代码
2017/10/25 Javascript
实例学习JavaScript读取和写入cookie
2018/01/29 Javascript
修改Nodejs内置的npm默认配置路径方法
2018/05/13 NodeJs
vue 解决无法对未定义的值,空值或基元值设置反应属性报错问题
2020/07/31 Javascript
Python基于正则表达式实现检查文件内容的方法【文件检索】
2017/08/30 Python
python绘制铅球的运行轨迹代码分享
2017/11/14 Python
python爬虫爬取快手视频多线程下载功能
2018/02/28 Python
基于python 爬虫爬到含空格的url的处理方法
2018/05/11 Python
python主线程捕获子线程的方法
2018/06/17 Python
python利用requests库进行接口测试的方法详解
2018/07/06 Python
python 计算两个列表的相关系数的实现
2019/08/29 Python
python进程池实现的多进程文件夹copy器完整示例
2019/11/27 Python
Python字典dict常用方法函数实例
2020/11/09 Python
彪马英国官网:PUMA英国
2019/02/11 全球购物
总经理岗位职责
2013/11/09 职场文书
销售找工作求职信
2013/12/20 职场文书
经典导游欢迎词大全
2014/01/16 职场文书
竞争上岗演讲稿范文
2014/05/12 职场文书
加油口号大全
2014/06/13 职场文书
人力资源管理专业求职信
2014/07/23 职场文书
上课随便讲话检讨书
2014/09/12 职场文书
心得体会格式及范文
2016/01/25 职场文书
【海涛dota解说】海涛小满开黑4v5被破两路翻盘潮汐第一视角解说
2022/04/01 DOTA