• setTimeout和setInterval从入门到精通


    我们在日常web前端开发中,经常需要用到定时器方法。
    前端中的定时器方法是浏览器提供的,并不是ECMAScript规范中的。是window对象的方法。

    浏览器中的定时器有两种,

    1. 一种是每间隔一定时间执行一次,循环往复。比如每隔一秒执行一次,六十秒过后执行了60次。
    2. 一种是过了一定时间执行一次,只执行一次。比如隔一秒后执行一次,过了十万八千秒后也只在第一秒执行了一次,仅有的一次。
    1. 第一种是:window.setInterval
    2. 第二种是:window.setTimeout

    由于window在浏览器中是全局对象,可以省略,所以常用setInterval和setTimeout

    这两个方法的参数是一模一样的:

    1. 正常使用的话,至少需要有两个参数。【这一条可以忽略】
    2. 不想出现报错的话,至少必须得一个参数。
    3. 都可以传递无数个参数。
    4. 第一个参数是要执行的js语句,有三种以上的情况
      1. 字符串:"console.log('I am Pelli');"【可读性差,推荐指数:1/10】
      2. 匿名函数:function(){console.log("I am Pelli")}【可读性强,推荐指数:8/10】
      3. 函数名:showName【可读性强,耦合度低,可装逼,推荐指数:9/10】
      4. 其他乱七八糟的内容【非正常程序员干的事情】
    5. 第二参数是用来描述时间,以毫秒为单位。有一种以上的情况
      1. 数值【正常情况下】
      2. 数值以外的其他数据类型【异类程序员干的事情】

     1 // 第一个参数的第一种情况
     2 var timer1 = setTimeout("console.log('I am Pelli')");
     3 // 第一个参数的第二种情况
     4 var timer2 = setTimeout(function(){
     5     console.log("I am Pelli");
     6 });
     7 // 第一个参数的第三种情况
     8 function showMyName(){
     9     console.log("I am Pelli");
    10 }
    11 var timer3 = setTimeout(showMyName);
    12 // !注意:由于以上三种情况都没有传递控制时间的第二个参数,第二个参数默认是0,都会在0秒后执行。
    以上三种方式的效果一模一样
    13 // 第一个参数的第四种情况【实际开发中基本上无用】 14 var timer4_0 = setTimeout(null); 15 var timer4_1 = setTimeout(12); 16 var timer4_2 = setTimeout({});//Uncaught SyntaxError: Unexpected identifier 17 var timer4_3 = setTimeout([]); 18 var timer4_4 = setTimeout(NaN); 19 var timer4_5 = setTimeout(false); 20 var timer4_6 = setTimeout(/I am Pelli/);

    综上所述:第一个参数的数据类型不能是引用类型,对象和数组不能直接传递(这和浏览器有关,不同的浏览器处理的方式不一样),其他的诸如字符串,数值,boolean,正则等,都可以直接传递,不过在实际使用中很少有人这么做,没什么实际的意义。

    接下来的描述,我们的第一个参数都是以第二种为准。

    现在我们来看第二个参数

    setTimeout和setInterval的第二个参数是用来描述时间的。以毫秒为单位。1s=1000ms[1秒=1000毫秒]
    该参数通常都是直接传递数值的,比如12,120,1000,2000之类的,数据类型为:“number”

    1 // 一秒以后输出:I am Pelli
    2 var timer5 = setTimeout(function(){
    3     console.log("I am Pelli");
    4 },1000);
    5 
    6 // 两秒后输出:我是沛笠
    7 var timer6 = setTimeout(function(){
    8     console.log("我是沛笠");
    9 },2000);

    当然咯,还有一些不正常的程序员会传递一些乱七八糟的参数。让我们来看看不正常的程序员会传递哪些参数

    第二个参数除了直接传递数值外,还可以传递非数值类型的值,如果该值能转换成数值类型,则时间间隔就是转换后的值,如果不能转换成数值类型,则时间间隔为默认值。

    //第二个参数将会被转换成0
    var timer7_0 = setTimeout("console.log('I am Pelli');","");
    
    //转换成0
    var timer7_1 = setTimeout("console.log('I am Pelli');",false);
    
    //转换成1
    var timer7_2 = setTimeout("console.log('I am Pelli');",true);
    
    //200
    var timer7_2 = setTimeout("console.log('I am Pelli');",true + 199);
    
    //不能转换成数值,默认0
    var timer7_3 = setTimeout("console.log('I am Pelli');",NaN);
    
    //转换成0
    var timer7_4 = setTimeout("console.log('I am Pelli');",[]);
    var timer7_5 = setTimeout("console.log('I am Pelli');",{});
    var timer7_6 = setTimeout("console.log('I am Pelli');",/I am Pelli/);
    var timer7_7 = setTimeout("console.log('I am Pelli');",function(){});

    可以看到,第二个参数也是可以传递各种各样的参数的,只要是能转换成数值类型的,最终都会转换成数值类型,如果没有转换成数值类型的,也不会报错,最终会使用默认时间值。默认值具体是多少,要看是哪种JS引擎。不同的JS引擎赋予的默认值是不一样的。
    不过我们作为一个正常的程序员,对于第二个参数只需要记住一点:传递数值,表示间隔时间

    以上我们用的是setTimeout,setInterval和setTimeout二者的参数一模一样,唯一的区别就是js语句执行的次数和时机。
    setInterval是循环执行
    setTimeout只执行一次

     1 var timer8_0 = setInterval(function(){
     2     console.log("我的微信号是:pelligit");
     3 },2000);
     4 
     5 var timer8_1 = setTimeout(function(){
     6     console.log("我的邮箱是:pelli_mail@163.com");
     7 },2000);
     8 
     9 // 我们来看看以上两个定时器的执行情况
    10 // timer8_0【从2s开始,每隔2s执行一次】
    11 // 0s:
    12 // 1s:
    13 // 2s: "我的微信号是:pelligit"
    14 // 3s:
    15 // 4s: "我的微信号是:pelligit"
    16 // 5s:
    17 // 6s: "我的微信号是:pelligit"
    18 // ......
    19 // ---------------------------------
    20 // timer8_1【只有第2s执行了一次】
    21 // 0s:
    22 // 1s:
    23 // 2s: "我的邮箱是:pelli_mail@163.com"
    24 // 3s:
    25 // 4s:
    26 // 5s:
    27 // 6s:
    28 // ......

    实际开发中学到这里就基本上可以完成大多数定时器任务了。只需要掌握两个参数:第一个是执行的语句,第二个是间隔时间。

    那么我们现在来做一个小玩意儿吧。用两种不同的方式做一个秒表。

    秒表一共三部分。【开始】按钮,【结束】按钮,时间显示区域。

    点击【开始】按钮,时间显示区域每一秒增加1。

    点击【结束】按钮,时间显示区域显示0,停止数秒。

    【HTML代码】

    <!-- 时间显示区域 -->
    <span id="seconds_content">0</span>
    
    <!-- 开始按钮 -->
    <button type="button" id="start" name="button">start</button>
    <!-- 结束按钮 -->
    <button type="button" id="stop" name="button">stop</button>

    【JavaScript代码】第一种,使用setInterval

    (function(){
        // 显示时间
        var seconds_content = document.getElementById("seconds_content");
        // 开始按钮【点击开始计时】
        var start_btn = document.getElementById("start");
        // 停止按钮【点击停止计时】
        var stop_btn = document.getElementById("stop");
    
        var count = 0;
        var time_count;
    
        // 给开始按钮添加点击事件,点击按钮,开始计时
        start_btn.addEventListener("click",function(event){
            seconds_content.innerHTML = count;
            time_count = setInterval(function(){
                count++;
                seconds_content.innerHTML = count;
            },1000);
        });
    
        // 给停止按钮添加事件,点击按钮,停止计时
        stop_btn.addEventListener("click",function(event){
            clearInterval(time_count);
            count = 0;
            seconds_content.innerHTML = count;
        });
    })();

    【JavaScript代码】第二种,使用setTimeout

    (function(){
        // 显示时间
        var seconds_content = document.getElementById("seconds_content");
        // 开始按钮【点击开始计时】
        var start_btn = document.getElementById("start");
        // 停止按钮【点击停止计时】
        var stop_btn = document.getElementById("stop");
    
        var count = 0;
        var time_count;
    
        function intervalLike(){
            time_count = setTimeout(function(){
                count++;
                seconds_content.innerHTML = count;
                // 循环调用自身,达到和setInterval一样的效果
                intervalLike();
            },1000);
        }
    
        // 给开始按钮添加点击事件,点击按钮,开始计时
        start_btn.addEventListener("click",function(event){
            intervalLike();
        });
    
        // 给停止按钮添加事件,点击按钮,停止计时
        stop_btn.addEventListener("click",function(event){
            clearInterval(time_count);
            count = 0;
            seconds_content.innerHTML = count;
        });
    })();

    最终效果如图:

    上面就是最简单的秒表程序。实际开发中,还有很多地方需要优化。秒表写完了,打开浏览器看看,是不是有点小兴奋啊。点击start,开始数秒,1,2,3,4,5,6,7,8......点击停止,停止数秒。再点击开始,1,2,3,4,5,6,7.....停止,开始,停止,开始,停止,开始,开始,开始,兴奋过头了,多点击了几次开始,,咦?怎么越来越快了?

    看代码可以发现有两个方法和setTimeout和setInterval很像,分别是clearTimeout和clearInterval,由名字就可以看到,前面有一个clear,是清除的意思。这两个都是用来清除定时器引用的

    有setTimeout就有clearTimeout
    有setInterval就有clearInterval

    var timer9 = setTimeout(function(){
        console.log('hello world');
    },2000);
    
    //清除上面的定时器,hello world将不会输出
    clearTimeout(timer9);

    timer9的值是setTimeout语句的一个标签,是js引擎对这条语句的记录值,是一个数字,可以传递给clearTimeout用来清除定时器。【以上代码clearTimeout除了可以传递timer9之外,还可以传递和timer9相等的数字】
    也就是说clearInterval和clearTimeout的参数是一个数值,该数值是指向要清除的定时器的一个引用地址值。
    根据观察发现,该值不是定时器代码的执行所需要的时间,也不是代码执行的间隔时间,单位不是毫秒数。而是随着浏览器启动的时间变长而增大。【谷歌浏览器中】
    我猜测这是一个指向定时器的一个地址引用,因为clearInterval和clearTimeout可以直接传递和定时器语句返回值相同的数值。


    具体情况,还得研究一下浏览器js引擎的底层。目前由于能力有限,暂未涉及。如果有朋友了解这个东西,欢迎补充完整。
    IE浏览器和火狐浏览器都是递增的,不过并不是从0开始的,也不是从1开始的。火狐是2,IE是5,谷歌浏览器的初始值就不确定了,随着时间的推移,该值会越来越大,不过虽然与时间有关,但是其与时间的关联方式目前还有待进一步研究,因为其既不是按着毫秒增长的,也不是按着秒增长的。

    还记得之前的秒表越来越快的问题吗?clearInterval和clearTimeout就是解决这个问题的。在适当的时间清除定时器是一个好的编码习惯。

    改进型秒表。再也不会越来越快了。不过计时不精确。

    // 显示时间
    var seconds_content = document.getElementById("seconds_content");
    // 开始按钮【点击开始计时】
    var start_btn = document.getElementById("start");
    // 停止按钮【点击停止计时】
    var stop_btn = document.getElementById("stop");
    
    var count = 0;
    var time_count;
    
    function setTimer(){
        time_count = setInterval(function(){
            count++;
            seconds_content.innerHTML = count;
        },1000);
    }
    
    // 给开始按钮添加点击事件,点击按钮,开始计时
    start_btn.addEventListener("click",function(event){
        count = seconds_content.innerHTML;
    
        // 清除原先的定时器
        clearInterval(time_count);
    
        // 重新开始计时
        setTimer();
    });
    
    // 给停止按钮添加事件,点击按钮,停止计时
    stop_btn.addEventListener("click",function(event){
        clearInterval(time_count);
        count = 0;
        seconds_content.innerHTML = count;
    });

    让我们来思考这样一个问题。当定时器的第一个参数是一个匿名函数或者是函数名的时候,而这个匿名函数或者函数名刚好需要传递参数,这个时候我们该怎么办?

    就像下面这样

    function plus(a,b,c){
        console.log(a,b,c);
    }
    
    var timer10_0 = setTimeout(plus,1001);
    
    var timer10_1 = setTimeout(function(a,b){
        console.log(a + b);
    },1000);

    这个时候,第三个参数,第四个参数,第五个参数......就发挥作用了。

    function plus(a,b,c){
        console.log(a,b,c);
    }
    
    //hello world hi
    var timer10_0 = setTimeout(plus,1001,"hello","world","hi");
    
    var timer10_1 = setTimeout(function(a,b){
        console.log(a + b);//300
    },1000,100,200);

    就是这么简单。

    关于JavaScript定时器,可以说的东西太多了,这里暂时先介绍到这里。下面留几个面试题,大家欣赏一下。

    // 1.下面的代码输出名字的顺序是什么?
    console.log("小明");
    var myname = setTimeout(function(){
        console.log("小华");
    },0);
    console.log("小丽");
    // 2.下面的代码,会弹出什么内容?
    var alert_things = setTimeout(function(){
        alert("Pelli");
    },2000);
    while(true){}
    // 3.下面的代码,会输出什么?
    setTimeout(function(){
        console.log(this);
    },1000);
    // 4.下面的代码,会输出什么?
    for (var i = 0; i < 3; i++) {
         setTimeout(function() {
             console.log(i);
         }, 0);
         console.log(i);
    }
    // 5.下面的代码,会弹出哪些东西?
    var len=4;
    while(len--){
        setTimeout(function(){
            alert(len);
        },0);
        alert(len);
    }
    // 6.下面的代码是什么结果?
    var timer = setInterval(function(){
        console.log("hello world");
    },0);
    
    clearInterval(timer);
    // 7.下面的代码会弹出什么?
    var t = true;
    window.setTimeout(function (){
        t = false;
    },0);
    while (t){}
    alert('end');

    另外有一些开发过程中的最佳实践:

      1.setTimeout和setInterval的第一个参数不建议传递字符串,传递字符串和eval有一样的问题,会造成性能方面的问题,甚至引起安全问题。

      2.建议用setTimeout模拟setInterval,不建议使用setInterval。页面中setTimeout和clearTimeout不配合使用基本上不会出现问题,因为setTimeout只执行一次,不会造成累加的问题,使用setInterval的话,强烈建议在适当时候使用clearInterval,在这种情况,很容易出现问题。

      

    欢迎各位程序媛和我一起交流讨论。男女通吃。

    github地址:www.github.com/pelligit/

    我是沛笠,Pelli,

    微信号:pelligit

    QQ:2653807423

  • 相关阅读:
    对我影响最大的老师
    秋季学习总结
    介绍自己
    搭建新环境的准备工作
    我的技术博客开通啦!!
    java数组及数组的插入,删除,冒泡算法
    包(package)以及面向对象三个基本特征(继承)的介绍
    常用的Arrays类和二维数组以及二分法的介绍
    构造方法、封装、关键字(this、static)和代码块的介绍
    类和对象的介绍
  • 原文地址:https://www.cnblogs.com/pelli/p/6225858.html
Copyright © 2020-2023  润新知