setTimeout和setInterval

作为DOM本身十分重要的2个异步执行函数,初学者感觉这个很不好理解,我简单写一写我的理解

setTimeout (func, millisec);
setInterval(func, millisec);

这两个方法在形式看起来很相似,第一个参数是异步执行的函数(用字符串表示的代码也可以,不过很少这样用),第二个参数是时间(ms)。但其实这两个函数还是有很大区别的。

说的通俗一点,setTimeout()让电脑一定时间以后再执行一段代码,执行完了就拉倒,例如下面这段代码:

setTimeout("alert('5 seconds!')", 5000); //5秒后弹出提示框

再看看setInterval(),不同的在于每过一段时间会重复执行一段代码。比如在浏览器中输出一个时间,每一秒变化一下。

var clk = document.getElementById("clk"); //clk是一个div
setInterval(function(){
  var t = new Date();
  clk.innerHTML= t;
}, 200);
//每隔200ms显示一次时间,200ms会看着比1000ms更稳定一些

运行上面这个代码,如果你得到的时间不准确,那一定是你的电脑时间错了。
好了,总结一下:

setTimeout(func, m); //m毫秒后执行函数func,只执行一次(下文会讲怎么让它中止)
setInterval(func, m); //每隔m毫秒执行函数func,一直执行下去(下文会讲怎么让它停下来)

对于setInterval()的用途可能还比较好理解,但是对于setTimeout(),这里有一个问题——我们为什么要让一段代码过一段时间在执行?

这里就产生了一个很重要的概念,在本文一开始就提到过——异步!什么是异步?
举个通俗一点的例子:比如现在有烧水和擦桌子两件事,如果你一定要用热水擦桌子,那你就必须先等着水开了,才能擦桌子,这个就是同步;如果你不在乎用什么水擦桌子,那一般人都会先烧水,在烧水的同时就开始擦桌子了,这个就是异步。

对于计算机而言,这个逻辑和正常生活不一样:同时执行就是异步,先后执行就是同步!当然这是一种简单的理解,并不严谨,毕竟在计算机内部计时器脉冲、操作系统进程、网络通信中都有同步和异步的概念。

有了异步执行这个概念,那小编可以负责的说setTimeout()setInterval()都是异步执行的。其实2015年出的ES6在异步这里下了很大的功夫,提出了async function(){}Promise对象、Generator函数、Object.observe函数等很多新概念,不过这里不谈这些概念,但理解这些异步概念绝不能像这篇文章里面简单粗暴!!!

好了现在可以回答上面那个问题了,我们之所以会用到setTimeout()setInterval()更多还是为了异步执行代码,以此提高代码的执行速度。到此这两个方法的就讲完了。。。等等!!!方法?方法会不会有返回值呀!
没错,这两个函数都有返回值,至于这个值是个什么并不重要,我们只需要知道这个值能干什么,我们给上面的那段代码添点东西:

var clk = document.getElementById("clk"); //clk是一个div
var time = setInterval(function(){
  var t = new Date()
  clk.innerHTML= t;
}, 200);

var btn = document.getElementById("btn"); //btn是一个按钮
btn.onclick = function(){
  clearInterval(time);
};

这段代码我们获得了setInterval()的返回值,把返回值传给了clearInterval()方法,这样实现了点击按钮结束的对应setInterval()的反复调用,终于它可以停下来了。

同样的方法,利用clearTimeout() 方法可取消由 setTimeout() 函数定义的异步操作,我们如法炮制得到一个取消延迟事件的按钮(当然,如果这个事件已经执行了再点这个按钮就没意义了):

var alt = setTimeout("alert('5 seconds!')", 5000); //5秒后弹出提示框
var btn = document.getElementById("btn"); //btn是一个按钮
btn.onclick = function(){
  clearTimeout(alt);
};

如果用setIntervalsetTimeout调用的函数是一个有参数的函数怎么办?

function f(a, b){/*...*/}
var num = 2;
setTimeout("f(num, 3)", 1000);  //第一种方法
setTimeout(function(){fun(num, 3);}, 1000);  //第二种方法

这一下,setTimeout()setInterval()已经理解了,看它们相似点其实也不少,否则怎么会容易被搞混呢?其实我们可以使用setTimeout()实现setInterval()的功能,就像下面一段代码:

function mySetInterval(code, ms){
  if(typeof code === "string")  //不要忘了第一个参数可以是字符串
    eval(code);
  else if(typeof code === "function")
    code();
  else throw new Error("code cannot be run");  //当第一个参数传入不是字符串或函数时报错
  setTimeout(function(){
    mySetInterval(code, ms);  //递归调用
  },ms);
}
mySetInterval("document.write('helloWorld!<br />')", 1000);//调用方法不变,第一个参数也可以是函数

这里值得强调的是eval()并不是什么好东西,它在全局运行,对字符串并不能有效检查(会让JSLint失效),还会调用编译器降低效率,同时带来安全隐患。所以尽量不要用eval(),也不要给setTimeoutsetInterval传入字符串,因为系统也是用eval实现这个功能的。
这段代码只是用这样一种方式表示setTimeout()setInterval()的关系,便于读者理解。并不表示这样做有什么好,更不表示编译器也这么实现setInterval(),因为这样的递归效率不高占用资源却不少,而且它没法停止。

如果你已经掌握了上面的内容,那么下面可以更深入的理解一下异步了。

以setTimeout为例:

//alert(1);
//setTimeout("alert(2)", 0);
//alert(3);
//alert(3);

上面这段代码会如何输出呢?实际输出是:1 -> 3 -> 3 ->2,为什么会这样,难道setTimeout(func, 0)不是立即执行?没错。想理解这个问题,必须简单理解浏览器是如何处理异步函数的。

一个页面在浏览器显示出来至少需要3个线程,分别是js引擎,GUI渲染,事件触发。其中事件触发是独立于其他2个执行的,而js和GUI是相互排斥的,也就是说同一个时间二者只有一个在工作。好了,这说明js引擎是单线程执行的,当第二行的setTimeout执行以后,js引擎把func(第一个参数)放入异步队列(浏览器再开一个线程),然后继续向下执行,此后,当js引擎空闲下来才会把异步执行的结果插入原来js线程中。是不是这样呢,我们让代码说话:

    var finish = true;
    setTimeout(function(){
        finish = false;  //1s后,改变isEnd的值
    }, 0);
    while(finish);
    alert('finished');  //永远不会执行

上方这段代码是个死循环,就因为js引擎不能空闲下来,异步函数也就没有执行。下面这个实际问题可以很好的理解这个:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title></title>
</head>
<style>
</style>
<body>
  <button id='firstBtn'>first</button>
    <div id='firstHint'>click 'first' button to calculate.</div>
  <br/>
  <button id='secondBtn'>second</button>
  <div id='secondHint'>click 'second' button to calculate.</div>
</body>
<script>
  function calc(s) {
      var result = 0;
      for (var i = 0; i < 1000000000; i++) {
          result = result + i;
      }
    //debugger;
    document.querySelector(s).innerHTML = 'Done' ;
  }

  document.querySelector('#firstBtn').onclick = function () {
      document.querySelector('#firstHint').innerHTML = 'calculating....';
      calc('#firstHint');
  };

    document.querySelector('#secondBtn').onclick = function () {
      document.querySelector('#secondHint').innerHTML = 'calculating....';
      setTimeout(function(){
      calc('#secondHint');
    }, 0);
  };
</script>
</html>

我们用了一很大的循环模拟一个耗费时间的计算。

分析一下:第一个按钮看不到calculating。由于js引擎的事件处理也是异步的,而for循环是同步的,设置文字为calculating的语句被放在了for循环结束,因为只有此时js才有空闲处理异步队列,for结束了以后,文字被设置为calculating,继而变为Done,所以我们看不到这个过程了,在代码中debugger的位置停一下,这个过程就清晰的呈现了出来。

为了解决这个问题,第二个按钮引入了setTimeout这样,异步事件click执行,函数内第一个语句被送入队列,而后setTimeout里那个匿名函数被送入队列,此时js引擎有空闲,于是输出calculating,异步队列移动,继续执行calc,这样就是我们想看到的结果了。

不足之处请多指点。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容