你知道setTimeout是如何运行的吗?


Posted in Javascript onAugust 16, 2016

大家看下如下代码,猜猜执行结果:

var start = new Date;
setTimeout(function(){
 console.log('时间流逝了:'+(new Date - start)+'毫秒');
}, 200);
while (new Date - start < 1000) {}
console.log(1);
function doSoming(){
 setTimeout(function(){
  console.log('时间又流逝了:'+(new Date - start)+'毫秒');
 },10);
}
doSoming();
while (new Date - start < 2000) {}
console.log(2);

结果是:
约1秒后输出:1,
再过约1秒后输出:2,
接着才立即输出:时间流逝了: 2002 毫秒
最后输出:时间又流逝了: 2003 毫秒

您猜对了没?
这里通过setTimeout来延迟执行的函数都被推到最后才执行了;
原理如下:

在现有浏览器环境中,Javascript执行引擎是单线程的,主线程的语句和方法,会阻塞定时任务的运行,在Javascript执行引擎之外,存在一个任务队列,当在代码中调用setTimeout()方法时,注册的延时方法会挂到浏览器内核其他模块处理,当延时方法到达触发条件,即到达设置的延时时间时,该模块再将要执行的方法添加至该模块的任务队列中。这一过程与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,才会从该模块的任务队列中顺序提取任务来执行,这期间的时间,可能大于注册任务时设置的延时时间;

浏览器在空闲状态下,会不断的尝试从模块的任务队列中提取任务,这称为事件循环模型;

再回头看下前面的代码,第二个setTimeout()的延迟方法的延迟时间是10毫秒,比第一个要早触发啊!为什么执行结果却在后面?因为它被之前的代码阻塞了约1000.5~1001毫秒了(视浏览器的处理速度),等他挂到处理模块,等到触发时间添加进任务队列时,第一个setTimeout()的延迟方法早就被添加到模块的任务队了,而引擎主线程是按顺序提取得,所以,你应该懂了吧?

现在,如果把上面的while (new Date - start < 1000) {}改成while (new Date - start < 189) {}或者是while (new Date - start < 190) {},结果又是什么?我就不多说了!各刷新浏览器20遍,自己看结果吧!

而setInterval()方法和setTimeout()地位是相同的,调用setInterval()方法时,注册的延时方法挂到模块处理,每当触发时间到达,就往任务队列添加一次要执行的方法;

下面来具体看看setTimeout的语法: 
var timeID = window.setTimeout(func,delay,[param1,param2,...]); 
var timeID = window.setTimeout(code,delay);
setTimeout和setInterval是Window对象的方法(可省略window),第二个之后的可选参数(IE9及旧版不支持)是传递给func的参数,每次调用他们时都会返回一个数字ID(在浏览器中打印出来就只是个数字,而本人在webstorm中打印出来发现它实际是一个对象,有很多个属性),这个ID保持着它对应的setTimeout或setInterval的相关信息,主要用来在中模块中和任务队列中清除(或关闭)掉它们(用方法clearTimeout(ID)和clearInterval(ID))。
如果你需要向你的回调函数内传递一个参数以下是兼容IE的写法 

if (document.all && !window.setTimeout.isPolyfill) {
 var __nativeST__ = window.setTimeout;
 window.setTimeout = function (vCallback, nDelay, param1, param2,param3) {
 var aArgs = Array.prototype.slice.call(arguments, 2);
 return __nativeST__(vCallback instanceof Function ? function () {
  vCallback.apply(null, aArgs);
 } : vCallback, nDelay);
 };
 window.setTimeout.isPolyfill = true;
}

一个常见的错误出现在循环中使用闭包 

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

上面的代码只会输出数字 10 十次。为什么?闭包! 

当 console.log 被调用的时候,虽然匿名函数保持对外部变量 i 的引用,但此时 for循环已经结束, i 的值被修改成了 10. 

为了得到想要的结果,需要在每次循环中创建变量 i 的拷贝。 

为了正确的获得循环序号,最好使用 匿名包裹器(其实就是我们通常说的自执行匿名函数)。 

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

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。
 当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。
 有另一个方法完成同样的工作;那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。 

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

还有一个重要应用:函数节流(throttle)与函数去抖(debounce)
请看我从网上收集的一些资料:
JavaScript-性能优化之函数节流(throttle)与函数去抖(debounce)

参考链接: 
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout 
http://www.alloyteam.com/2015/10/turning-to-javascript-series-from-settimeout-said-the-event-loop-model/#prettyPhoto

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

Javascript 相关文章推荐
JavaScript高级程序设计(第3版)学习笔记4 js运算符和操作符
Oct 11 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(五)可移动地图的实现
Jan 23 Javascript
JavaScript对内存分配及管理机制详细解析
Nov 11 Javascript
如何判断微信内置浏览器(通过User Agent实现)
Sep 01 Javascript
jquery中filter方法用法实例分析
Feb 06 Javascript
Vue.js每天必学之数据双向绑定
Sep 05 Javascript
jQuery在ie6下无法设置select选中的解决方法详解
Sep 20 Javascript
利用forever和pm2部署node.js项目过程
May 10 Javascript
js原生实现移动端手指滑动轮播图效果的示例
Jan 02 Javascript
webpack项目调试以及独立打包配置文件的方法
Feb 28 Javascript
微信小程序CSS3动画下拉菜单效果
Nov 04 Javascript
vue如何自动化打包测试环境和正式环境的dist/test文件
Jun 06 Javascript
深入理解(function(){... })();
Aug 16 #Javascript
关于JSON与JSONP简单总结
Aug 16 #Javascript
json与jsonp知识小结(推荐)
Aug 16 #Javascript
浅谈JS继承_借用构造函数 &amp; 组合式继承
Aug 16 #Javascript
JS读写CSS样式的方法汇总
Aug 16 #Javascript
浅谈JS继承_寄生式继承 &amp; 寄生组合式继承
Aug 16 #Javascript
jQuery EasyUI Tab 选项卡问题小结
Aug 16 #Javascript
You might like
《魔兽争霸3》重制版究竟重制了什么?玩家:这么糊弄真的好吗?
2020/05/04 魔兽争霸
php连接函数implode与分割explode的深入解析
2013/06/26 PHP
php实例化一个类的具体方法
2019/09/19 PHP
PHP接入微信H5支付的方法示例
2019/10/28 PHP
input+select(multiple) 实现下拉框输入值
2009/05/21 Javascript
基于jQuery的弹出消息插件 DivAlert之旅(一)
2010/04/01 Javascript
javascript中onclick(this)用法介绍
2013/04/19 Javascript
artdialog的图片/标题以及关闭按钮不显示的解决方法
2013/06/27 Javascript
javascript如何使用bind指定接收者
2014/05/04 Javascript
javascript面向对象快速入门实例
2015/01/13 Javascript
javascript简单比较日期大小的方法
2016/01/05 Javascript
不使用script导入js文件的几种方法
2016/10/27 Javascript
详解vuex 中的 state 在组件中如何监听
2017/05/23 Javascript
Angularjs 双向绑定时字符串的转换成数字类型的问题
2017/06/12 Javascript
基于Vue2x的图片预览插件的示例代码
2018/05/14 Javascript
Js中将Long转换成日期格式的实现方法
2018/06/05 Javascript
ES6使用export和import实现模块化的方法
2018/09/10 Javascript
微信小程序结合mock.js实现后台模拟及调试
2019/03/28 Javascript
JS计算两个数组的交集、差集、并集、补集(多种实现方式)
2019/05/21 Javascript
JavaScript利用键盘码控制div移动
2020/03/19 Javascript
[05:20]卡尔工作室_DOTA2新手教学_DOTA2超强新手功能
2013/04/22 DOTA
利用Python的Django框架生成PDF文件的教程
2015/07/22 Python
python分割列表(list)的方法示例
2017/05/07 Python
JavaScript实现一维数组转化为二维数组
2018/04/17 Python
Python基于pandas实现json格式转换成dataframe的方法
2018/06/22 Python
使用Python写一个量化股票提醒系统
2018/08/22 Python
python之cv2与图像的载入、显示和保存实例
2018/12/05 Python
python利用跳板机ssh远程连接redis的方法
2019/02/19 Python
Django框架搭建的简易图书信息网站案例
2019/05/25 Python
解决Pyinstaller 打包exe文件 取消dos窗口(黑框框)的问题
2019/06/21 Python
Charles&Keith美国官方网站:新加坡快时尚鞋类和配饰零售商
2019/11/27 全球购物
2014年元旦活动方案
2014/02/15 职场文书
分公司经理任命书
2014/06/05 职场文书
2019年员工晋升管理制度范本!
2019/07/08 职场文书
详解MySQL多版本并发控制机制(MVCC)源码
2021/06/23 MySQL
SQL SERVER实现连接与合并查询
2022/02/24 SQL Server