js中setTimeout和setInterval性能详解总结

  1. 云栖社区>
  2. 博客>
  3. 正文

js中setTimeout和setInterval性能详解总结

webmirror 2017-06-22 16:33:21 浏览4009
展开阅读全文

在写H5游戏时经常需要使用定时刷新页面实现动画效果,比较常用即setTimeout()以及setInterval()

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,而setInterval()则是在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。也就是说setTimeout()只执行一次,setInterval()可以执行多次。两个函数的参数也相同,第一个参数是要执行的code或句柄,第二个是延迟的毫秒数

setTimeout

描述

var timeoutID = window.setTimeout(code,millisec);

timeoutID:定时器ID号,它可以在clearTimeout()函数中被用来清除定时器。
code:一个被执行的代码串或函数
millisec:延迟的时间,单位毫秒。如果没有指定,默认为0

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式

注:调用过程中,可以使用clearTimeout(id_of_settimeout)终止

window.setTimeout或setTimeout,两个写法基本一样,只不过window.setTimeout将setTimeout函数作为全局window对象的一个属性来引用

setTimeout(function timeout(){ 
    console.log(Math.floor(Math.random()*100 + 1)); 
},500)

window.setTimeout方法调用函数有两种方法:

function hello(){ 
    console.log("hello"); 
} 
window.setTimeout(hello,500);   //不可以有参数
window.setTimeout("hello()",500);   //可以有参数

无论window.setTimeout还是window.setInterval,在使用函数名作为调用句柄时都不能带参数,而在许多场合必须要带参数,这就需要想方法解决。例如对于函数hello(_name),它用于针对用户名显示欢迎信息: var userName="jack";

function hello(_name){  
    alert("hello,"+_name);   //根据用户名显示欢迎信息
 }

这时,如果企图使用以下语句来使hello函数延迟3秒执行是不可行的:

window.setTimeout(hello(userName),3000);

这将使hello函数立即执行,并将返回值作为调用句柄传递给setTimeout函数,其结果并不是程序需要的。而使用字符串形式可以达到想要的结果:

window.setTimeout("hello(userName)",3000);

如果在延时期限到达之前取消演示执行,可以使用window.clearTimeout(timeoutId)方法

function hello(){  
    alert("hello");
}
var id=window.setTimeout(hello,5000);
document.onclick=function(){  
    window.clearTimeout(id);
}

这样,如果要取消显示,只需单击页面任何一部分,就执行了window.clearTimeout方法,使得超时操作被取消

除了前两个参数,setTimeout还允许添加更多的参数。它们将被传入推迟执行的函数

setTimeout(function(a,b){
  console.log(a+b);
},1000,1,2);  //3

上面代码中,setTimeout共有4个参数。最后两个参数,将在1000毫秒之后回调函数执行时,作为回调函数的参数。

IE 9.0以下版本,只允许setTimeout有两个参数。这时有三种解决方法,第一种是自定义setTimeout,使用apply方法将参数输入回调函数;第二种是在一个匿名函数里面,让回调函数带参数运行,再把匿名函数输入setTimeout;第三种使用bind方法,把多余的参数绑定在回调函数上面,生成一个新的函数输入setTimeout

除了参数问题,setTimeout还有一个需要注意的地方:被setTimeout推迟执行的回调函数是在全局环境执行,这有可能不同于函数定义时的上下文环境

var x = 1;
var o = {
    x: 2,
    y: function(){
    console.log(this.x);
  }
};
setTimeout(o.y,1000);  //1

上面代码输出的是1,而不是2,这表示回调函数的运行环境已经变成了全局环境

再看一个不容易发现错误的例子

function User(login) {
    this.login = login;
    this.sayHi = function() {
        console.log(this.login);
    }
}
var user = new User('John');
setTimeout(user.sayHi, 1000);  //undefined

上面代码只会显示undefined,因为等到user.sayHi执行时,它是在全局对象中执行,所以this.login取不到值。为了防止出现这个问题,一种解决方法是将user.sayHi放在函数中执行

setTimeout(function() {
  user.sayHi();
}, 1000);      //John

user.sayHi是在函数作用域内执行,而不是在全局作用域内执行,所以能够显示正确的值

另一种更通用的解决方法,则是采用闭包,将this与当前运行环境绑定

document.getElementById('click-ok').onclick = function() {
    var self = this;
    setTimeout(function() { 
        self.value='OK';
    }, 100);
}     //John

上面代码中,setTimeout指定的函数中的this,总是指向定义时所在的DOM节点

setInterval

setInterval函数的参数及用法和setTimeout函数一样,不同的是,setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式

var tt = 10; 
function timego(){ 
    tt--; 
    console.log(tt); 
    if(tt==0){ 
        window.location.href='/'; 
        return false; 
    } 
};
window.setInterval("timego()",1000);

我们定义一个定时器timer,使用setInterval()每隔1秒调用一次timego()。这样timego会执行10次,每次数字tt会减1,直到为0。那么如果想停止定时器,可以使用以下代码:

与setTimeout一样,除了前两个参数,setInterval方法还可以接受更多的参数,它们会传入回调函数

function f(){
    for (var i=0;i<arguments.length;i++){
        console.log(arguments[i]);
    }
}
setInterval(f, 1000, "Hello World");
// Hello World
// Hello World
// Hello World
// ...

setInterval指定的是,“开始执行”之间的间隔,因此实际上,两次执行之间的间隔会小于setInterval指定的时间。假定setInterval指定,每100毫秒执行一次,每次执行需要5毫秒,那么第一次执行结束后95毫秒,第二次执行就会开始。如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始

var i = 1;
var timer = setInterval(function() { 
  alert(i++);
}, 2000);

上面代码每隔2000毫秒跳出一个alert对话框。如果用户一直不点击“确定”,整个浏览器就处于“堵塞”状态,后面的执行就一直无法触发,会累积起来。举例来说,第一次跳出alert对话框后,用户过了6000毫秒才点击“确定”,那么第二次、第三次、第四次执行将累积起来,它们之间不会再有等待间隔。

为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。上面代码用setTimeout,可以改写如下

var i = 1;
var timer = setTimeout(function() {
  alert(i++);
  timer = setTimeout(arguments.callee, 2000); //arguments的主要用途是保存函数参数,这个对象有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数
}, 2000);

clearTimeout(对象) 清除已设置的setTimeout对象;clearInterval(对象) 清除已设置的setInterval对象

window.clearInterval(timer);

其实setTimeout()也可以实现每隔一段时间重复执行某个函数,所以在js中setTimeout 和 setInterval 都经常被用来做网页上的定时器,允许为它指定一个毫秒数作为间隔执行的时间。当被启动的程序需要在非常短的时间内运行,我们就会给她指定一个很小的时间数,或者需要马上执行的话,我们甚至把这个毫秒数设置为0,但事实上,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动

setTimeout与setInterval基本原理详细分析

for(var i = 1; i <= 3; i++) {
    setTimeout(function(){
        console.log(i); // 4 4 4
    },100);
}

很多人会理所当然的认为for循环会分别打印出1,2,3. 但是事实不是这样的,会输出3次4. 要理解为什么会打印三次4,我们先来理解setTimeout这个函数吧,很多人会认为上面的setTimeout的意思是这样的:在100毫秒后执行setTimeout的回调函数,其实这样的理解是有误的,其实setTimeout与setInterval真正的含义如下:

setTimeout:在指定的毫秒数后,将定时任务处理的函数添加到执行队列的队尾

setInterval:按照指定的周期(以毫秒数计时),将定时任务处理函数添加到执行队列的队尾

setTimeout与setInterval都是异步的,所以我们现在可以来理解下上面循环为什么一直都是4呢?其实调用setTimeout时候,会有一个延时事件排入队列,然后setTimeout调用之后的那行代码运行,接着是再下一行代码,直到再也没有任何代码了,javascript虚拟机才会问,队列里还有吗?如果队列中至少有一个事件适合于触发,比如上面的setTimeout函数,则会调用setTimeout那个函数。所以上面的代码先for循环,循环结束,而 i === 4一直递增,直到不再满足i<=3为止。所以就打印了3个4

setTimeout(function(){
    console.log("打印我,我是异步执行的");
},100);
console.log("我是新来的,我要先执行");

运行结果是:先打印出 “我是新来的,我先执行”这句代码,接着打印”打印我,我是异步执行的”代码

setTimeout(f,0)

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f,0),那么会立刻执行吗?

答案是不会。因为上一段说过,必须要等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才会执行setTimeout指定的任务。也就是说,setTimeout的真正作用是,在“任务队列”的现有事件的后面再添加一个事件,规定在指定时间执行某段代码。setTimeout添加的事件,会在下一次Event Loop执行

setTimeout(f,0)将第二个参数设为0,作用是让f在现有的任务(脚本的同步任务和“任务队列”中已有的事件)一结束就立刻执行。也就是说,setTimeout(f,0)的作用是,尽可能早地执行指定的任务

setTimeout(function() { 
  console.log("Timeout");
}, 0);
function a(x) { 
    console.log("a() 开始运行");
    b(x);
    console.log("a() 结束运行");
}
function b(y) { 
    console.log("b() 开始运行");
    console.log("传入的值为" + y);
    console.log("b() 结束运行");
}
console.log("当前任务开始");
a(42);
console.log("当前任务结束");
// 当前任务开始
// a() 开始运行
// b() 开始运行
// 传入的值为42
// b() 结束运行
// a() 结束运行
// 当前任务结束
// Timeout

上面代码说明,setTimeout(f,0)必须要等到当前脚本的所有同步任务结束后才会执行。

0毫秒实际上达不到的。根据HTML 5标准,setTimeOut推迟执行的时间,最少是4毫秒。如果小于这个值,会被自动增加到4。另一方面,浏览器内部使用32位带符号的整数,来储存推迟执行的时间。这意味着setTimeout最多只能推迟执行2147483647毫秒(24.8天),超过这个时间会发生溢出,导致回调函数将在当前任务队列结束后立即执行,即等同于setTimeout(f,0)的效果

应用

可以调整事件的发生顺序

比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,我们先让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)

var input = document.getElementsByTagName('input[type=button]')[0];
input.onclick = function A() {
  setTimeout(function B() {
    input.value +=' input'; 
  }, 0)
};
document.body.onclick = function C() {
  input.value += ' body'
};

面代码在点击按钮后,先触发回调函数A,然后触发函数C。在函数A中,setTimeout将函数B推迟到下一轮Loop执行,这样就起到了,先触发父元素的回调函数C的目的了

文本框问题

用户自定义的回调函数,通常在浏览器的默认动作之前触发。比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的的

document.getElementById('input-box').onkeypress = function(event) {
  this.value = this.value.toUpperCase();
}

上面代码想在用户输入文本后,立即将字符转为大写。但是实际上,它只能将上一个字符转为大写,因为浏览器此时还没接收到文本,所以this.value取不到最新输入的那个字符。只有用setTimeout改写,上面的代码才能发挥作用

document.getElementById('my-ok').onkeypress = function() {
  var self = this;
  setTimeout(function() {
    self.value = self.value.toUpperCase();
  }, 0);
}

上面代码将代码放入setTimeout之中,就能使得它在浏览器接收到文本之后触发

计算量大、耗时长的任务

由于setTimeout(f,0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到setTimeout(f,0)里面执行

var div = document.getElementsByTagName('div')[0];

// 写法一
for(var i=0xA00000;i<0xFFFFFF;i++) {
  div.style.backgroundColor = '#'+i.toString(16);
}
// 写法二
var timer;
var i=0x100000;
function func() {
  timer = setTimeout(func, 0);
  div.style.backgroundColor = '#'+i.toString(16);
  if (i++ == 0xFFFFFF) clearInterval(timer);
}
timer = setTimeout(func, 0);

上面代码有两种写法,都是改变一个网页元素的背景色。写法一会造成浏览器“堵塞”,而写法二就能就不会,这就是setTimeout(f,0)的好处

另一个使用这种技巧的例子是,代码高亮的处理。如果代码块很大,就会分成一个个小块,写成诸如setTimeout(highlightNext, 50)的样子,进行分块处理

网友评论

登录后评论
0/500
评论
webmirror
+ 关注