javascript之定时器

 在JavaScript中,定时器看似简单,其实挺复杂的,与JavaScript的运行机制。我们都知道JavaScript是单线程的,这里说的单线程指的是JavaScript引擎是单线程的,浏览器是多线程的。JavaScript的运行机制不是本篇文章讨论范围,本文主要讨论setTimeout。定时器很常用,也很容易出现在面试题中,这篇文章将围绕定时器的语法,常见例子,以及一些要注意的地方和容易弄错的地方。

一、setInterval语法

 语法setInterval(str,time);setInterval(function,time,parmam1,param2…);第一个参数为字符串或者函数,第二个参数为定时的时间,第三个参数可以作为传给函数的参数(可选)。返回值为一个定时器的ID(数字);一般将这个ID传给clearinterval()清除定时器。setInterval定时器是间歇性调用,即每隔设定的时间调用一次,在不加干涉的情况下,间歇调用将会一直执行到页面卸载。例如

setInterval(function(){
    console.log(1);
},2000);

每隔2秒打印一次值,这段如果不使用clearInterval清除定时器则会一直输出1

二、setTimeout语法

 setTimeout的语法跟setInterval一样,只不过setTimeout不是间歇调用,而是超时调用,而是在设定时间是执行一次定时器里面的代码,注意是执行一次。清除定时器使用clearTimeout().例如

setTimeout(function(){
    console.log(1);
},2000);

这段代码只打印一次1,在编写程序过程中建议多使用setTimeout代替setInterval。

三、执行机制

 setInterval与setTimeout是异步执行的,也就是说在同一个循环中,等到其他同步代码执行完后,再执行定时器的代码。如这段代码:

console.log(1);
setTimeout(function(){
    console.log(2);
},1000);
console.log(3);

输出的顺序是1,3,2,而不是1,2,3;定时器中设定的时间不一定就是在1秒就打印出2,而是等其他同步代码执行完后,再等待一秒打印。如果前面的代码耗时很长那么可能要很久才执行。那么现在来理解一下

setTimeout(f,0);

当定时为0的时候是立即打印吗?当然不是,就像前面说的,它会等其他同步任务执行完后再执行,那么将时间定为0就表示当其他同步任务执行完后立即执行定时器里面的代码。setTimeout(f,0)的应用:
1. 用来改变事件的执行顺序:例如我们想要在表单中输入英文字母时立即将它转换为小写字母,加如我们使用这段代码:

document.getElementById('in').onkeypress=function(event){
            this.value=this.value.toLowerCase();
        }

那么它只能在键盘输入下一个字符时才把上一个字符变为小写,但我们需要的是输入字符立即转换。此时可以使用setTimeout(f,0)解决这个问题

document.getElementById('in').onkeypress=function(event){
            var _this=this;
            setTimeout(function(){
                _this.value=_this.value.toLowerCase();
            },0);   
        }

2.由于setTimeout(f,0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行。所以在处理那些计算量大、耗时长的任务,常常会被放到几个小部分(容易造成阻塞),分别放到setTimeout(f,0)里面执行。简单来讲就是任务拆分。

//写法一
var div=document.getElement('div1');
        for(var i=0xA00000;i<0xFFFFF;i++){
            div.style.backgroundColor='#'+i.toString(16);
        }
//写法二
    var div=document.getElement('div1');
        var timer;
        var i=0x100000;
        function f(){
            timer=setTimeout(f,0);
            div.style.backgroundColor='#'+i.toString(16);
            if(i++==0xFFFFF) clearTimeout(timer);
        }
        timer=setTimeout(f,0);

四、setTimeout与闭包

 经过前面的分析,现在我们来看一下小例子

for(i=0;i<5;i++){
    console.log(i);
}

相信这段代码输出什么大家肯定不陌生吧。那么接下来这段代码呢?

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

没错,这段代码并不会如我们所愿的输出0,1,2,3,4;而是输出连续输出5个5。为什么呢?如果我们在setTimeout后面再加一个console.log(i),通过控制台可以看到,先输出0、1、2、3、4,再输出5个5.

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

这就跟我前面说的setTimeout是异步的有关,它会等整个循环同步任务执行完后再执行。那么我们可以通过闭包来解决问题,每循环一次,便立即将i值传入自执行函数中,传给setTimeout。

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

为什么这下面这段代码失败了呢?

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

这是因为没有将每一次循环的i值传给setTimeout.

五、正常任务和微任务

 我们来考虑一个问题,当setTimeout和Promise同时出现时,两个都是异步执行的,那么谁先执行呢?考虑下面代码:

console.log(1);
setTimeout(function(){
    console.log(2);
},0);
Promise.resolve().then(function(){
    console.log(3);
});

通过运行我们可以发现输出的结果是1、3、2;即setTimeout在Promise之后执行。原因是setTimeout是正常任务,Promise是微任务。setTimeout是在下一次Event Loop中执行,而Promise是在本轮Event Loop结束时执行。正常任务有

  • setInterval
  • setTimeout
  • setImmediate
  • I/O
  • 各种事件(比如鼠标单击事件)的回调函数

六、一些注意事项

 还记得上面的这个例子吗

document.getElementById('in').onkeypress=function(event){
            var _this=this;
            setTimeout(function(){
                _this.value=_this.value.toLowerCase();
            },0);   
        }

在这里将当前this保存下来,不然在settimeout运行时this会指向全局对象。现在来详细了解一下setTimeout指向的问题。由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined。例如这段代码

var a=1;
function obj(){
    this.a=2,
    this.getA=function(){
        console.log(this.a);
    }

    this.getAlate=function(){
        setTimeout(function(){
            console.log(this.a);
        },1000);
    }
}
var o=new obj();

o.getA();//2
o.getAlate();//1 this指向全局window

从输出结果可看出,setTimeout中的this指向了window对象。解决这种问题一般有三种方法

  • 将this保存下来,通过闭包访问
var a=1;
function obj(){
    var that=this;//保存this
    this.a=2,
    this.getA=function(){
        console.log(this.a);
    }
    this.getAlate=function(){
        setTimeout(function(){
            console.log(that.a);
        },1000);
    }
}
var o=new obj();
o.getA();//2
o.getAlate();//2
  • 使用bind函数改变this指向

var a=1;
function obj(){
    this.a=2,
    this.getA=function(){
        console.log(this.a);
    }
    this.getAlate=function(){
        setTimeout(function(){
            console.log(this.a);
        }.bind(this),1000);//绑定this
    }
}
var o=new obj();

o.getA();//2
o.getAlate();//2
  • 使用es6的箭头函数
var a=1;
function obj(){
    this.a=2,
    this.getA=function(){
        console.log(this.a);
    }
    this.getAlate=function(){
        setTimeout(()=>{
            console.log(this.a);
        },1000);
    }
}
var o=new obj();
o.getA();//2
o.getAlate();//2

参考文章:
MDN文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值