You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<body><divid="box"style="width: 100px;height: 100px;"></div><script>
var box = document.querySelector('#box');
// 写法一
for (var i = 0xA00000; i <0xFFFFFF;i++){div.style.backgroundColor='#'+i.toString(16);}// 写法二vartimer=null;vari=0x100000;functionfn(){timer=setTimeout(fn,0);box.style.backgroundColor='#'+i.toString(16);if(i++===0xFFFFFF)clearTimeout(timer);}timer=setTimeout(fn,0);
</script></body>
JavaScript提供定时执行代码的功能,叫做定时器(timer),主要由
setTimeout()
和setInterval()
这两个函数来完成。它们向任务队列添加定时任务。js是运行于单线程的环境中的,定时器仅仅只是计划代码在未来的某个时间执行(但是并不保证在该时间点一定执行)。执行时机是不能保证的,因为在页面的生命周期中,不同时间可能有其他代码在控制js进程。在页面下载完后的代码运行、事件处理程序、Ajax回调函数都必须使用同样的线程来执行。实际上,浏览器负责进行排序,指派某段代码在某个时间点运行的优先级。
如上图所示:我们可以把
javascript
想象成在时间线上运行的。当页面载入时,首先执行是任何包含在<script>
元素中的代码,通常是页面生命周期后面要用到的一些简单的函数和变量的声明,有时候也包含一些初始数据的处理。在这之后,javascript
进程将等待更多代码执行,当进程空闲时,下一个代码会被触发并立刻执行。例如:当点击某个按钮时,onclick
事件处理程序会立刻执行,只要javascript
进程处于空闲状态。除了
javascript主执行进程
外,还有一个需要在进程下一次空闲时执行的代码队列。随着页面在其生命周期中的推移,代码会按照执行顺序添加到队列中。例如:当某个按钮被按下,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里执行。当接收到某个Ajax响应时,回调函数的代码会被添加到队列。在javascript
中没有任何代码是立刻执行的,但是一旦进程空闲则尽快执行。定时器对队列的工作方式是: 当特定时间过去后将代码插入。注意,给队列添加代码并不意味着对它立刻执行,而只能表示它会尽快执行。例如:设定一个150ms后执行的定时器不代表到了150ms代码就立刻执行,它表示代码会在150ms后被加入到队列中。如果在这个时间点,队列中没有其他东西,那么这段代码就会被执行,表面上看上去就好像代码就在精确的时间点上执行了。其他情况,代码可能明显等待更长时间才执行。
在上图中:给按钮设置了一个事件处理程序,该事件处理程序设置了一个
250ms
后调用的定时器。点击该按钮后,首先将onclick
事件处理程序加入队列。该事件处理程序执行后才设置定时器,再有250ms
后,指定的代码才被添加到队列中等待执行。对于定时器而言:我们要记住指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。如果上图中的
onclick
事件处理程序执行了300ms
,那么定时器的代码至少要在定时器设置之后的300ms
后才会被执行。队列中所有的代码都要等到js
进程空闲之后才能执行,而不管它们是如何添加到队列中的。上图中,尽管在
255ms
处添加了定时器代码,但是这个时候不能执行,因为onclick
事件处理程序还在运行。定时器代码最早的执行时机在300ms
处,即onclick
事件处理程序结束之后。1. setTimeout
setTimeout
函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。上面代码中,
setTimeout
函数接受两个参数,第一个参数func|code
是将要推迟执行的函数名或者一段代码,第二个参数delay
是推迟执行的毫秒数。1.1 demo1(第一个参数是code)
上面代码会先输出111和333,然后等待2秒再输出222。特别注意:
console.log(2)
必须以字符串的形式,作为setTimeout
的参数。1.2 demo2(第一个参数是函数)
如果推迟执行的是函数,就直接将函数名作为setTimeout的参数。
1.3 setTimeout参数
除了前面提到的两个参数,setTimeout还允许更多的参数。它们将依次传入推迟执行的函数(回调函数)。
上面代码中,setTimeout共有4个参数。最后那两个参数(1和2),将在1秒之后回调函数执行时,作为回调函数的参数。
1.4 setTimeout的回调函数是对象的方法
上面代码输出的是1,而不是2。因为当
obj.y
在1秒后运行时,this
所指向的已经不是obj
了,而是全局环境window
。1.4.1 解决方法一(将
obj.y
放入一个函数)上面代码中,
obj.y
放在一个匿名函数之中,这使得obj.y
在obj
的作用域执行,而不是在全局作用域内执行,所以能够显示正确的值。1.4.2 解决方法二(使用
bind
方法,将obj.y
这个方法绑定在obj
上面)1.4.3
ES6
声明变量的六种方法ES5
只有两种声明变量的方法:var命令和function命令
。ES6
除了添加let和const
命令,后面章节还会提到,另外两种声明变量的方法:import命令和class
命令。所以,ES6
一共有6
种声明变量的方法。1.4.4 顶层对象的属性
顶层对象,在浏览器环境指的是
window
对象,在Node
中指的是global
对象。需要注意的是在ES5
中,顶层对象的属性与全局变量是等价的。上面代码中,顶层对象的属性赋值与全局变量的赋值,是一回事。
2. setInterval(重复的定时器)
使用
setInterval
创建的定时器确保了定时器代码规则地插入队列中。但是该方法的问题在于: 定时器代码可能在代码再次被添加到队列之前还没有执行完成,结果导致定时器代码连续运行好几次,而之间没有任何停顿。然而,javascript
引擎够聪明,能避免这个问题。当使用setInterval
时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。这确保了定时器代码加入到队列中的最小时间间隔为指定间隔。上面代码中,每隔1秒就输出一个2,会无限运行下去,直到关闭当前窗口。与
setTimeout
一样,除了前两个参数,setInterval
方法还可以接受更多的参数,它们会传入回调函数。上面代码每隔100毫秒,设置一次div元素的透明度,直至其完全透明为止。
如上图所示:重复定时器有两个问题:1.某些间隔会被跳过;2.多个定时器的代码执行之间的间隔可能会比预期的小。假设,某个
onclick
事件处理程序使用setInterval
设置了一个200ms
间隔的重复定时器。如果事件处理程序花费了300ms
多一点的时间完成,同时定时器代码也花费了差不多的时间,就会跳过一个间隔同时运行着一个定时器代码。在上图的例子中:第一个定时器在
205ms
时被添加到队列中,但是直到过了300ms
处才能够执行。当执行这个定时器代码时,在405ms
处又给队列添加了另外一个副本。在下一个间隔,即605ms
处。第一个定时器代码扔在运行,同时在队列中已经存在一个定时器代码的实例。结果导致在这个时间点上的定时器代码不会被添加到队列中。同时,当5ms
处添加的定时器代码结束后,405ms
处添加的定时器代码就立刻执行。上述代码中,链式调用了
setTimeout
。每次函数执行的时候都会创建一个新的定时器。第二个setTimeout
调用使用了arguments.callee
来获取对当前执行的函数的引用,并为其设置另外一个定时器。这样做的好处是:在前一个定时器代码执行完成之前,不会向队列中插入新的定时器代码,确保不会有任何缺失的间隔。而且,可以保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免连续的运行。上面代码可以确保,下一次执行总是在本次执行结束之后的2秒开始。3. clearTimeout和clearInterval
setTimeout和setInterval
函数,都返回一个整数值,表示计数器编号。将该整数传入clearTimeout和clearInterval
函数,就可以取消对应的定时器。利用这一点,可以写一个函数,取消当前所有的setTimeout定时器。
4. 实例应用:debounce
有时,我们不希望回调函数被频繁调用。比如:用户填入网页输入框的内容,希望通过
Ajax
方法传回服务器,jQuery
的写法如下:5. 运行机制
setTimeout
和setInterval
的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。上面代码的
setTimeout
,指定100ms
以后运行一个任务。但是,如果后面的veryLongTask
函数(同步任务)运行时间非常长,过了100ms
还无法结束,那么被推迟运行的someTask
就只有等着,等到veryLongTask
运行结束,才轮到它执行。上面代码中,
setInterval
要求每隔1秒,就输出一个2。但是,紧接着的sleep
语句需要3秒才能完成,那么setInterval
就必须推迟到3秒之后才开始生效。特别注意: 生效后setInterval
不会产生累积效应,即不会一下子输出三个2,而是只会输出一个2。6. setTimeout(f, 0)
6.1 含义
setTimeout
的作用是:将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0)
,那么会立刻执行吗?6.2 应用
setTimeout(f, 0)
有几个非常重要的用途。它的一大应用是:可以调整事件的发生顺序。 比如:网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果想让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)
。参考文档
The text was updated successfully, but these errors were encountered: