[原]JavaScript的window.setTimeout()方法

    在测试某Web应用时,其中有段函数,用于动态的插入合适大小的图片到当前页面中。原理是:先利用JavaScript的Image对象读入图片,然后判断图片大小,当图片尺寸过大(超过阀值)时,给页面插入的HTML代码中,加入width和height的属性值。测试时发现,该功能有时候会失效,这时,width和height都会等于0。经分析,问题在于创建Image对象,并读入图片的过程会有延迟,不能立即得到图片的属性。而JavaScript是异步处理机制的,在图片未读完前,即继续往下执行。所以,导致获得的图片大小均为0。
    既然知道原因,那么处理方法很简单,就是在读取图片后,做个延时的处理,详细可参考:这里。
    没想到,在调整我的代码时,发现对setTimeout()方法的使用还有不少误解的地方,特整理如下。

一、准备工作
为了后续的描述方便,并熟悉一下JavaScript扩展原型对象的方式,先创建一个格式化输出当前时间的函数:

function formatDate() {
  var year=this.getFullYear();
  var month=this.getMonth()+1;
  var date=this.getDate();
  var hour=this.getHours();
  var minute=this.getMinutes();
  var second=this.getSeconds();
  var millisecond=this.getMilliseconds();
  var output = 'Now is: '+year+'-'+month+'-'+date+' '+hour+':'+minute+':'+second+' '+millisecond;
  return output;
}
Date.prototype.format=formatDate;
function printNow(string) {
  var today = new Date();
  if (typeof(string) == 'undefined') string='';
  var box = document.getElementById('box');
  var boxContent = box.innerHTML;
  outputString = boxContent+string+today.format()+'<br />';
  box.innerHTML=outputString;
  return outputString;
}
var timeout;

HTML代码是:

<div id="box"></div>

这样,当使用printNow()函数时,即会输出当前时间到页面中。

二、window.setTimeout()方法
回到正题,先看看window.setTimeout函数的定义:Gecko DOM Reference-DOM window Reference-window.setTimeout。
其中,给出该函数的用法:

var timeoutID = window.setTimeout(func, delay, [param1, param2, …]);
var timeoutID = window.setTimeout(code, delay);

使用说明:

引用
*  timeoutID is the ID of the timeout, which can be used later with window.clearTimeout.
* func is the function you want to execute after delay milliseconds.
* code in the alternate syntax, is a string of code you want to execute after delay milliseconds. (Using this syntax is not recommended for the same reasons as using eval())
* delay is the number of milliseconds (thousandths of a second) that the function call should be delayed by. Note that the actual delay may be longer, see Notes below.
Note that passing additional parameters to the function in the first syntax does not work in Internet Explorer.

正如红色标记的文字说明,code和param1、param2等参数,因考虑到兼容性问题,一般不建议使用。delay 的单位是毫秒(ms)。所以,最后的用法可简化为:

var timeoutID = window.setTimeout(func, delay);

格式很简单,注意事项可就多了。
※ 为什么写成window.setTimeout()方法?
因为需要明确,setTimeout函数是window对象的方法,平时写setTimeout()只是简写。由于面向的是window对象,所以后面不少的问题都与该前提有关,继续往下阅读时,请务必留意。

三、基本使用
1、简单用法
在HTML代码<div id="box"></div>后面加入:(因为输出信息需加到该div中,故必须在其后面运行脚本)

<script type="text/javascript">
  printNow();
  clearTimeout(timeout);
  timeout=setTimeout(printNow,1000); //留意printNow的写法
</script>

输出结果:

引用
Now is: 2010-6-11 23:8:25 78
Now is: 2010-6-11 23:8:26 78

简单吧?继续看下面的。

2、参数func 的写法
不知道您有没有留意,在上面setTimeout函数后面,延迟执行的函数,只有一个函数名(表示指向该函数的指针)。我们可以把setTimeout函数中,func参数理解为“指向延迟执行的函数的指针”。
下面的写法也可以:

timeout=setTimeout("printNow()",1000);
timeout=setTimeout('printNow()',1000);

但是下面的写法是错误的:

timeout=setTimeout('printNow',1000); //不会报错,但实际没有执行
timeout=setTimeout(printNow(),1000); //不会延迟,马上执行

第二句若用于图片加载函数,会报“内存不足”的错误。见这里。

3、带参数的func
如果延迟执行的函数需要传递参数,那该如何写呢?
首先,下面的写法肯定是错误的:

timeout=setTimeout(printNow(10),1000); //同样不会延迟,马上执行

此外,正如开头提到的,setTimeout()方法是面向window对象的,全称是window.setTimeout()。所以,一般情况下,在func中传递的参数,必须是window对象的变量(即全局变量)
例如:

<script type="text/javascript">
  printNow();
  var tips='After setTimeOut:<br />';
  clearTimeout(timeout);
  timeout=setTimeout("printNow(tips)",1000);
</script>

这里的,tips就是window对象的变量,你可以用alert(window.tips);语句验证一下。上面的输出结果为:

引用
Now is: 2010-6-11 23:13:26 593
After setTimeOut:
Now is: 2010-6-11 23:13:27 593

代码执行很正常。

不过,如果给func传递的不是window对象的变量,那么又会如何呢?
我们把上面执行的代码放到一个函数中,让tips 在函数中定义:

<script type="text/javascript">
function run() {
  printNow();
  var tips='After setTimeOut:<br />';
  clearTimeout(timeout);
  timeout=setTimeout("printNow(tips)",1000);
}
run();
</script>

执行一下。浏览器会报'tips'未定义,因为,这时tips已经不再是window对象的变量了。这样的情况很常见,解决办法就是,利用JavaScript的特性:匿名函数
代码改为:

function run() {
  printNow();
  var tips='After setTimeOut:<br />';
  clearTimeout(timeout);
  timeout=setTimeout(function() {
    printNow(tips);
  },1000);
}
run();

再执行,可输出正常结果:

引用
Now is: 2010-6-11 23:23:49 402
After setTimeOut:
Now is: 2010-6-11 23:23:50 424

※ 无论在func中是否有参数需要传递,使用匿名函数的方式都可以使用,并且可读性更强。

四、注意事项
1、执行顺序
当初,在使用setTimeout()方法时,我一直想当然的认为其是类似其他语言中的sleep功能,但实际情况并不是这样的。
举例来说,我们把上面的代码改为:

function run() {
  printNow();
  var tips='After setTimeOut:<br />';
  clearTimeout(timeout);
  timeout=setTimeout(function() {
    var returnValue = printNow(tips);
  },1000);
  document.write(returnValue);
}
run();

运行时,会报“returnValue is not defined”。
为什么会这样呢?因为setTimeout()方法与sleep是不同的。
如PHP中的sleep,在执行到该语句时,程序会睡眠,不再往下运行,当达到设定的时间后,才继续往下执行;
而setTimeout()并不如此,当程序遇到setTimeout()语句时,会启动另一子进程计算等待时间,但程序并不会中断在该的位置,而会继续往下执行。当子程序到达设定时间时,会运行setTimeout()中指定的func 函数。

所以,在上面的程序中,因为returnValue是由setTimeout()中的printNow函数返回才能赋值的,但因程序没有等待,故其值为空。
我们把returnValue的输出改一下:

document.write(typeof(returnValue));

开始的结果为:

引用
Now is: 2010-6-12 0:7:47 328
undefined

最终是:

引用
Now is: 2010-6-12 0:7:47 328
After setTimeOut:
Now is: 2010-6-12 0:7:48 328
undefined

明白了这一点,当我们使用setTimeout()方法时,就必须考虑:不要依赖其执行func 函数的返回值来使用,而应把setTimeout()的func 函数作为最后一步的动作来处理,把需执行的操作都放到func 函数中。

2、setTimeout()面向的对象
前面已经多次提到了,setTimeout()是面向window对象的,在Gecko DOM Reference的介绍中,有如下一段话:

引用
The 'this' problem
Code executed by setTimeout() is run in a separate execution context to the function from which it was called. As a consequence, the this keyword for the called function will be set to the window (or global) object, it will not be the same as the this value for the function that called setTimeout. This issue is explained in more detail in the JavaScript reference.

也就是说,在执行setTimeout()时,this关键字面向的也是window对象。
这在JavaScript的原型对象中使用setTimeout()时特别重要,例如:

var box = document.getElementById('box');
function myClass(){
  this.num=0;
}
myClass.prototype.count=function(){
  this.num++;
  var boxContent = box.innerHTML;
  box.innerHTML = boxContent+this.num;
  if(this.num>10){return;}
  var self=this; //先把当前的this指向对象(window对象)保留下来
  setTimeout(function() {self.count();},1000);
}
var myObj=new myClass();
myObj.count();

结果为:

引用
1234567891011

下面的几种写法都是错误的:

setTimeout("this.count()",1000);
setTimeout("count()",1000);
setTimeout(count,1000);

它们都没有合适的使用this所指向的window对象。

3、关于clearTimeout()方法
该方法主要用于取消setTimeout()方法的执行。当setTimeout()启动后,会返回一个timeoutID。当运行clearTimeout()方法就可以清空该值,并终止setTimeout()的执行。
除了可用于停止外,为了避免多次执行setTimeout()方法,导致延迟时间重叠等问题,建议在每次执行该方法前,都先用clearTimeout()清掉timeoutID。

※ 以上关于setTimeout()方法的说明,同样适用于window.setInterval()方法。
其区别在于:

引用
window.setTimeout()
在执行时,它从载入后延迟指定的时间去执行一个表达式或者是函数,仅执行一次;与window.clearTimeout()方法一起使用。
window.setInterval()
在执行时,它从载入页面后每隔指定的时间执行一个表达式或者是函数(功能类似于递归函数),重复执行;和与window.clearInterval()方法一起使用。

五、参考资料
MDC window.setTimeout
精解 window.setTimeout()使用方式与参数传递问题!
setTimeout() 在js类中的使用方法
setTimeout和setInterval的使用
为什么只是load,10个图,就会出现“内存不够”的错误?

IE 和FF 获取class属性的问题
JavaScript 的in 操作符
JavaScript 的 keyCode与键盘对应表
再次理解DOM的节点属性

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/104518.html

(0)
上一篇 2021年8月25日
下一篇 2021年8月25日

相关推荐

发表回复

登录后才能评论