JavaScript中的定时器

时间:2022-08-14 00:09:18

定时器

1、setTimeout

这个方法用于在指定的毫秒数之后执行某个函数,返回定时器的句柄

混合的 setTimeout()方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。

语法

let timeoutID = window.setTimeout(func[, delay, param1, param2, ...]);
let timeoutID
= scope.setTimeout(code[, delay]);
let timeoutID
= window.setTimeout(function, milliseconds);

说明:

  • timeoutID 是该延时操作的数字ID, 此ID随后可以用来作为window.clearTimeout方法的参数.
  • func 是你想要在delay毫秒之后执行的函数.
  • code 在第二种语法,是指你想要在delay毫秒之后执行的代码字符串 (使用该语法是不推荐的, 不推荐的原因和eval()一样)
  • delay 是延迟的毫秒数 (一秒等于1000毫秒),函数的调用会在该延迟之后发生。如果省略该参数,delay取默认值0。实际的延迟时间可能会比 delay 值长,原因请查看下面的备注。

需要注意的是,IE9 及更早的 IE 浏览器不支持第一种语法中向延迟函数传递额外参数的功能。如果你想要在IE中达到同样的功能,你必须使用一种兼容代码

备注: 在Gecko 13之前 (Firefox 13.0 / Thunderbird 13.0 / SeaMonkey 2.10), Gecko会给延迟函数传递一个额外的参数,该参数表明了此次延迟操作实际延迟的毫秒数.现在,这个非标准的参数已经不存在了.

例子

下文的例子在网页中设置了两个简单的按钮,以触发 setTimeout 和 clearTimeout 方法:按下第一个按钮会在 2s 后显示一个警告对话框,并将此次 setTimeout 的延时 ID 保存起来。按下第二个按钮可以取消这次延时调用行为。

HTML 内容

<p>Live Example</p>
<button onclick="delayedAlert();">Show an alert box after two seconds</button>
<p></p>
<button onclick="clearAlert();">Cancel alert before it happens</button>

JavaScript 内容

var timeoutID;
function delayedAlert() {
timeoutID
= window.setTimeout(slowAlert, 2000);
}
function slowAlert() {
alert(
"That was really slow!");
}
function clearAlert() {
window.clearTimeout(timeoutID);
}

回调参数

如果你需要向你的回调函数内传递一个参数, 而且还需要兼容IE9及以前的版本, 由于IE不支持传递额外的参数 (setTimeout() 或者 setInterval()都不可以) ,但你可以引入下面的兼容代码.该代码能让IE也支持符合HTML5标准的定时器函数.

/*\
|*|
|*| IE-specific polyfill which enables the passage of arbitrary arguments to the
|*| callback functions of javascript timers (HTML5 standard syntax).
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/window.setInterval
|*|
|*| Syntax:
|*| var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
|*| var timeoutID = window.setTimeout(code, delay);
|*| var intervalID = window.setInterval(func, delay[, param1, param2, ...]);
|*| var intervalID = window.setInterval(code, delay);
|*|
\
*/

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

if (document.all && !window.setInterval.isPolyfill) {
var __nativeSI__ = window.setInterval;
window.setInterval
= function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
var aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeSI__(vCallback instanceof Function ? function () {
vCallback.apply(
null, aArgs);
} : vCallback, nDelay);
};
window.setInterval.isPolyfill
= true;
}

IE Only Fix

如果你需要单独的针对IE9及之前浏览器的 hack 写法,你可以使用 JavaScript 条件注释:

/*@cc_on
// conditional IE < 9 only fix
@if (@_jscript_version <= 9)
(function(f){
window.setTimeout =f(window.setTimeout);
window.setInterval =f(window.setInterval);
})(function(f){return function(c,t){var a=[].slice.call(arguments,2);return f(function(){c.apply(this,a)},t)}});
@end
@
*/

或者使用更加清晰的 IE HTML 条件注释:

<!--[if lte IE 9]><script>
(
function(f){
window.setTimeout
=f(window.setTimeout);
window.setInterval
=f(window.setInterval);
})(
function(f){return function(c,t){
var a=[].slice.call(arguments,2);
return f(function(){c.apply(this,a)},t)}
});
</script><![endif]-->

 

另一种方法是使用匿名函数包裹你的回调函数,这种方式要消耗更多资源:

var intervalID = setTimeout(function() { myFunc("one", "two", "three"); }, 1000); 

此外,也可使用 function's bind

setTimeout(function(arg1){}.bind(undefined, 10), 1000);

关于"this"的问题

当你向 setTimeout() (或者其他函数也行)传递一个函数时,该函数中的this会指向一个错误的值.

解释

setTimeout()调用的代码运行在与所在函数完全分离的执行环境上. 这会导致,这些代码中包含的 this 关键字会指向 window (或全局)对象,这和所期望的this的值是不一样的.查看下面的例子:

myArray = ["zero", "one", "two"];
myArray.myMethod
= function (sProperty) {
alert(arguments.length
> 0 ? this[sProperty] : this);
};

myArray.myMethod();
// prints "zero,one,two"
myArray.myMethod(1); // prints "one"
setTimeout(myArray.myMethod, 1000); // prints "[object Window]" after 1 second
setTimeout(myArray.myMethod, 1500, "1"); // prints "undefined" after 1,5 seconds
//
let's try to pass the 'this' object
setTimeout.call(myArray, myArray.myMethod, 2000); // error: "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO: Illegal operation on WrappedNative prototype object"
setTimeout.call(myArray, myArray.myMethod, 2500, 2); // same error

正如你所看到的一样,我们没有任何方法将this对象传递给回调函数.

解决方案

一个可用的解决 "this" 问题的方法是使用两个非原生的setTimeout() 和 setInterval() 全局函数代替原生的.该非原生的函数通过使用Function.prototype.call 方法激活了正确的作用域.下面的代码显示了应该如何替换:

// Enable the passage of the 'this' object through the JavaScript timers

var __nativeST__ = window.setTimeout, __nativeSI__ = window.setInterval;

window.setTimeout
= function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
var oThis = this, aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeST__(vCallback instanceof Function ? function () {
vCallback.apply(oThis, aArgs);
} : vCallback, nDelay);
};

window.setInterval
= function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
var oThis = this, aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeSI__(vCallback instanceof Function ? function () {
vCallback.apply(oThis, aArgs);
} : vCallback, nDelay);
};

备注: 这两个替换也让 IE支持了符合 HTML5 标准的定时器函数。所以也能作为一个 polyfills

新特性检测:

myArray = ["zero", "one", "two"];
myArray.myMethod
= function (sProperty) {
alert(arguments.length
> 0 ? this[sProperty] : this);
};

setTimeout(alert,
1500, "Hello world!"); // the standard use of setTimeout and setInterval is preserved, but...
setTimeout.call(myArray, myArray.myMethod, 2000); // prints "zero,one,two" after 2 seconds
setTimeout.call(myArray, myArray.myMethod, 2500, 2); // prints "two" after 2,5 seconds

针对这个问题并没有原生的解决方案。

注:JavaScript 1.8.5 引入了 Function.prototype.bind() 方法,该方法允许显式地指定函数调用时 this 所指向的值 。该方法可以帮助你解决 this 指向不确定的问题。

使用bind的例子:

myArray = ["zero", "one", "two"];
myBoundMethod
= (function (sProperty) {
console.log(arguments.length
> 0 ? this[sProperty] : this);
}).bind(myArray);

myBoundMethod();
// prints "zero,one,two" because 'this' is bound to myArray in the function
myBoundMethod(1); // prints "one"
setTimeout(myBoundMethod, 1000); // still prints "zero,one,two" after 1 second because of the binding
setTimeout(myBoundMethod, 1500, "1"); // prints "one" after 1.5 seconds

备注

你可以使用 window.clearTimeout()来取消延迟操作。如果你希望你的代码被重复的调用 (比如每 N 毫秒一次),考虑使用 window.setInterval()

记住这一点:只有当调用setTimeout()的线程停止后,函数或代码段才能继续执行。

传递字符串字面量

setTimeout()传递一个字符串而不是函数会遭受到与使用eval一样的风险.

// 推荐
window.setTimeout(function() {
alert(
"Hello World!");
},
500);

// 不推荐
window.setTimeout("alert(\"Hello World!\");", 500);

字符串会在全局作用域内被解释执行,所以当setTimeout()函数执行完毕后,字符串中的变量不可用.

2、 setInterval

这个方法用于循环地执行某个函数,返回定时器的句柄

语法

var intervalID = window.setInterval(func, delay[, param1, param2, ...]);
var intervalID = window.setInterval(code, delay);

参数

  • intervalID 是此重复操作的唯一辨识符,可以作为参数传给clearInterval()
  • func 是你想要重复调用的函数。
  • code 是另一种语法的应用,是指你想要重复执行的一段字符串构成的代码(使用该语法是不推荐的,不推荐的原因和eval()一样)。
  • delay 是每次延迟的毫秒数 (一秒等于1000毫秒),函数的每次调用会在该延迟之后发生。和setTimeout一样,实际的延迟时间可能会稍长一点。

需要注意的是,IE不支持第一种语法中向延迟函数传递额外参数的功能.如果你想要在IE中达到同样的功能,你必须使用一种兼容代码 

备注: 在Gecko 13之前 (Firefox 13.0 / Thunderbird 13.0 / SeaMonkey 2.10), Gecko会给延迟函数传递一个额外的参数,该参数表明了此次延迟操作实际延迟的毫秒数。现在,这个非标准的参数已经不存在了。

重复调用一个函数或执行一个代码段,以固定的时间延迟在每次调用之间。返回一个 intervalID。

例1:基本用法

var intervalID = window.setInterval(animate, 500);

例2:两种颜色的切换

下面的例子里会每隔一秒就调用函数flashtext()一次,直至你通过按下Stop按钮来清除本次重复操作的唯一辨识符intervalID

<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>setInterval/clearInterval example</title>
<script type="text/javascript">
var nIntervId;

function changeColor() {
nIntervId
= setInterval(flashText, 500);
}

function flashText() {
var oElem = document.getElementById("my_box");
oElem.style.color
= oElem.style.color == "red" ? "blue" : "red";
}

function stopTextColor() {
clearInterval(nIntervId);
}
</script>
</head>

<body onload="changeColor();">
<div id="my_box">
<p>Hello World</p>
</div>
<button onclick="stopTextColor();">Stop</button>
</body>
</html>

例3:打字机的效果

下面这个例子通过键入、删除和再次键入所有NodeList中的符合的特定selector的字符,以达到打字机的效果。

<!doctype html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>JavaScript Typewriter - MDN Example</title>
<script type="text/javascript">
function Typewriter (sSelector, nRate) {

function clean () {
clearInterval(nIntervId);
bTyping
= false;
bStart
= true;
oCurrent
= null;
aSheets.length
= nIdx = 0;
}

function scroll (oSheet, nPos, bEraseAndStop) {
if (!oSheet.hasOwnProperty("parts") || aMap.length < nPos) { return true; }

var oRel, bExit = false;

if (aMap.length === nPos) { aMap.push(0); }

while (aMap[nPos] < oSheet.parts.length) {
oRel
= oSheet.parts[aMap[nPos]];

scroll(oRel, nPos
+ 1, bEraseAndStop) ? aMap[nPos]++ : bExit = true;

if (bEraseAndStop && (oRel.ref.nodeType - 1 | 1) === 3 && oRel.ref.nodeValue) {
bExit
= true;
oCurrent
= oRel.ref;
sPart
= oCurrent.nodeValue;
oCurrent.nodeValue
= "";
}

oSheet.ref.appendChild(oRel.ref);
if (bExit) { return false; }
}

aMap.length
--;
return true;
}

function typewrite () {
if (sPart.length === 0 && scroll(aSheets[nIdx], 0, true) && nIdx++ === aSheets.length - 1) { clean(); return; }

oCurrent.nodeValue
+= sPart.charAt(0);
sPart
= sPart.slice(1);
}

function Sheet (oNode) {
this.ref = oNode;
if (!oNode.hasChildNodes()) { return; }
this.parts = Array.prototype.slice.call(oNode.childNodes);

for (var nChild = 0; nChild < this.parts.length; nChild++) {
oNode.removeChild(
this.parts[nChild]);
this.parts[nChild] = new Sheet(this.parts[nChild]);
}
}

var
nIntervId, oCurrent
= null, bTyping = false, bStart = true,
nIdx
= 0, sPart = "", aSheets = [], aMap = [];

this.rate = nRate || 100;

this.play = function () {
if (bTyping) { return; }
if (bStart) {
var aItems = document.querySelectorAll(sSelector);

if (aItems.length === 0) { return; }
for (var nItem = 0; nItem < aItems.length; nItem++) {
aSheets.push(
new Sheet(aItems[nItem]));
/* Uncomment the following line if you have previously hidden your elements via CSS: */
// aItems[nItem].style.visibility = "visible";
}

bStart
= false;
}

nIntervId
= setInterval(typewrite, this.rate);
bTyping
= true;
};

this.pause = function () {
clearInterval(nIntervId);
bTyping
= false;
};

this.terminate = function () {
oCurrent.nodeValue
+= sPart;
sPart
= "";
for (nIdx; nIdx < aSheets.length; scroll(aSheets[nIdx++], 0, false));
clean();
};
}

/* usage: */
var oTWExample1 = new Typewriter(/* elements: */ "#article, h1, #info, #copyleft", /* frame rate (optional): */ 15);

/* default frame rate is 100: */
var oTWExample2 = new Typewriter("#controls");

/* you can also change the frame rate value modifying the "rate" property; for example: */
// oTWExample2.rate = 150;

onload
= function () {
oTWExample1.play();
oTWExample2.play();
};
</script>
<style type="text/css">
span.intLink, a, a:visited
{
cursor
: pointer;
color
: #000000;
text-decoration
: underline;
}

#info
{
width
: 180px;
height
: 150px;
float
: right;

padding
: 4px;
overflow
: auto;
font-size
: 12px;
margin
: 4px;
border-radius
: 5px;
/* visibility: hidden; */
}
</style>
</head>

<body>

<p id="copyleft" style="font-style: italic; font-size: 12px; text-align: center;">CopyLeft 2012 by <a href="https://developer.mozilla.org/" target="_blank">Mozilla Developer Network</a></p>
<p id="controls" style="text-align: center;">[&nbsp;<span class="intLink" onclick="oTWExample1.play();">Play</span> | <span class="intLink" onclick="oTWExample1.pause();">Pause</span>
| <span class="intLink" onclick="oTWExample1.terminate();">Terminate</span>&nbsp;]</p><div id="info">
Vivamus blandit massa ut metus mattis in fringilla lectus imperdiet. Proin ac ante a felis ornare vehicula. Fusce pellentesque lacus vitae eros convallis ut mollis magna pellentesque.
Pellentesque placerat enim at lacus ultricies vitae facilisis nisi fringilla. In tincidunt tincidunt tincidunt.
</div>
<h1>JavaScript Typewriter</h1>

<div id="article">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ultrices dolor ac dolor imperdiet ullamcorper. Suspendisse quam libero, luctus auctor mollis sed, malesuada condimentum magna.

Quisque in ante tellus, in placerat est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec a mi magna, quis mattis dolor. Etiam sit amet ligula
quis urna auctor imperdiet nec faucibus ante. Mauris vel consectetur dolor. Nunc eget elit eget velit pulvinar fringilla consectetur aliquam purus. Curabitur convallis, justo posuere porta egestas,
velit erat ornare tortor, non viverra justo diam eget arcu. Phasellus adipiscing fermentum nibh ac commodo. Nam turpis nunc, suscipit a hendrerit vitae, volutpat non ipsum.</p><form name="myForm">
<p>Phasellus ac nisl lorem: <input type="text" name="email" /><br />
<textarea name="comment" style="width: 400px; height: 200px;">
Nullam commodo suscipit lacus non aliquet. Phasellus ac nisl lorem, sed facilisis ligula. Nam cursus lobortis placerat. Sed dui nisi, elementum eu sodales ac, placerat sit amet mauris. Pellentesque

dapibus tellus ut ipsum aliquam eu auctor dui vehicula. Quisque ultrices laoreet erat, at ultrices tortor sodales non. Sed venenatis luctus magna, ultricies ultricies nunc fringilla eget. Praesent scelerisque
urna vitae nibh tristique varius consequat neque luctus. Integer ornare, erat a porta tempus, velit justo fermentum elit, a fermentum metus nisi eu ipsum. Vivamus eget augue vel dui viverra adipiscing congue ut massa.
Praesent vitae eros erat, pulvinar laoreet magna. Maecenas vestibulum mollis nunc in posuere. Pellentesque sit amet metus a turpis lobortis tempor eu vel tortor. Cras sodales eleifend interdum.</textarea></p><p><input type="submit" value="Send" />
</form>
<p>Duis lobortis sapien quis nisl luctus porttitor. In tempor semper libero, eu tincidunt dolor eleifend sit amet. Ut nec velit in dolor tincidunt rhoncus non non diam. Morbi auctor ornare orci, non euismod felis

gravida nec. Curabitur elementum nisi a eros rutrum nec blandit diam placerat. Aenean tincidunt risus ut nisi consectetur cursus. Ut vitae quam elit. Donec dignissim est in quam tempor consequat. Aliquam aliquam
diam non felis convallis suscipit. Nulla facilisi. Donec lacus risus, dignissim et fringilla et, egestas vel eros. Duis malesuada accumsan dui, at fringilla mauris bibStartum quis. Cras adipiscing ultricies fermentum.
Praesent bibStartum condimentum feugiat.</p><p>Nam faucibus, ligula eu fringilla pulvinar, lectus tellus iaculis nunc, vitae scelerisque metus leo non metus. Proin mattis lobortis lobortis. Quisque accumsan faucibus erat, vel varius tortor ultricies ac.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec libero nunc. Nullam tortor nunc, elementum a consectetur et, ultrices eu orci. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque a nisl eu sem vehicula egestas.</p></div>
</body>
</html>

倒计时:

<div class="box">
<span class="h time">12</span>
<span>:</span>
<span class="m time">12</span>
<span>:</span>
<span class="s time">55</span>
</div>
<script>
var div = document.body.children[0];
//所有的span
var spans = div.children;
var second = spans[4];
var minutes = spans[2];
console.log(minutes);
//每隔1秒钟修改秒数
setInterval(function(){
// 先获取旧的数字,自增,重新赋值
var old = parseInt(second.innerText);
var m = parseInt(minutes.innerText);
old
+=1;
//以上是秒的逻辑,到分的逻辑
//当秒数大于59,就满一分钟,分自增,秒重置
if(old >59){
old
= 0;
//获取分的值,自增,重新赋值
m += 1;
}
//小时的逻辑也是当分超过59,让小时自增1,分重置
second.innerText = old;
minutes.innerText
= m;
},
1000);
</script>

简单动画效果的实现

<!DOCTYPE html>
<html>
<head lang="zh-CN">
<meta charset="UTF-8">
<title></title>
<style>
*
{
margin
: 0;
padding
: 0;
}
#box
{
width
: 100px;
height
: 100px;
background-color
: #0a0;
/*margin-left: 500px;*/
/*一般做动画,都会使用定位,脱标定位 -- 绝对,固定*/
position
: absolute;
top
: 200px;
left
: 0px;
}
.boxw
{
width
: 100px;
height
: 100px;
background
: #cccccc;
float
: left;
}
</style>
</head>
<body>
<input type="button" value="开始运动" id="btn"/>
<div id="box"></div>
<div class="boxw"></div>
<script>
//动画原理:每隔一定的时间,修改能控制元素位置的属性,就能达到动起来的效果,当位置到达800的时候停下来
var curerntLeft = 0;
var box = document.getElementById("box");
document.getElementById(
"btn").onclick = function(){
var timerId = setInterval(function(){
//每次获取当前位置,进行自增,最后重新赋值
var step = 10;//每次移动的步长
curerntLeft += step;
box.style.left
= curerntLeft + "px";
//判断当前位置是否大于等于800,如果是,就停下来
if(curerntLeft >= 800){
clearInterval(timerId);
}
console.log(
123456);
},
20);//人的肉眼一般可识别的间隔为:0.03秒 = 30毫秒
}
</script>
</body>
</html>

回调参数

如果你想通过你的函数传递回一个参数,而且还要兼容IE,由于IE不支持传递额外的参数 (setTimeout() 或者 setInterval()都不可以) ,你可以引入下面的兼容代码。该代码能让IE也支持符合HTML5标准的定时器函数。

/*\
|*|
|*| IE-specific polyfill which enables the passage of arbitrary arguments to the
|*| callback functions of javascript timers (HTML5 standard syntax).
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/window.setInterval
|*|
|*| Syntax:
|*| var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
|*| var timeoutID = window.setTimeout(code, delay);
|*| var intervalID = window.setInterval(func, delay[, param1, param2, ...]);
|*| var intervalID = window.setInterval(code, delay);
|*|
\
*/

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

if (document.all && !window.setInterval.isPolyfill) {
var __nativeSI__ = window.setInterval;
window.setInterval
= function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
var aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeSI__(vCallback instanceof Function ? function () {
vCallback.apply(
null, aArgs);
} : vCallback, nDelay);
};
window.setInterval.isPolyfill
= true;
}