一、setTimeout 和 setInterval 的区别
setTimeout() 定义和用法:
定义:setTimeout()方法用于在指定毫秒数后再调用函数或者计算表达式(以毫秒为单位)
语法:
setTimeout(code,millisec)
参数:code必需,要调用的函数后要执行的 JavaScript 代码串;millisec必需,在执行代码前需等待的毫秒数。
说明:setTimeout() 只执行函数一次,如果需要多次调用可以使用 setInterval(),或者在函数体内再次调用setTimeout()
示例代码:延迟1秒弹出 Hello
// 延迟1秒弹出 Hello
setTimeout(function(){
alert("Hello");
}, 1000);
setInterval() 定义和用法:
定义:setInterval() 方法用于按照指定的周期(以毫秒计)来循环调用函数或计算表达式,直到 clearInterval() 被调用或窗口关闭,由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
语法:
setInterval(code,millisec[,"lang"])
参数:code必需,要调用的函数或要执行的JavaScript 代码串;millisec必须,周期性执行或调用 code 之间的时间间隔,以毫秒计。
说明:setInterval() 会不停的调用函数,直到clearInterval() 被调用或者窗口被关闭,由 setInterval() 返回的ID值可用作 clearInterval() 方法的参数。
示例代码:一直显示当前时间,点击停止不继续
<p>显示当前时间:</p>
<p id="demo"></p>
<button onclick="myStopFunction()">停止时间</button>
<!-- 一直显示当前时间,点击停止不继续 -->
<script>
var myVar = setInterval(function(){ myTimer() }, 1000);
function myTimer() {
var d = new Date();
var t = d.toLocaleTimeString();
document.getElementById("demo").innerHTML = t;
}
function myStopFunction() {
clearInterval(myVar);
}
</script>
区别总结:
setTimeout() 方法只运行一次,也就是说当达到设定的时间后就开始运行指定的代码,运行完后就结束了,次数是一次。
setInterval() 是循环执行的,即每达到指定的时间间隔就执行相应的函数或者表达式,只要窗口不关闭或 clearInterval() 调用就会无限循环下去。
二、知识储备
1、浏览器是个多进程应用
首先你要知道 浏览器是个多进程应用,那这些进程里都包含哪些:
- Browser进程:浏览器的主进程(负责协调、主控),只有一个,作用:
- 负责浏览器界面显示,与用户交互。如前进,后退等
- 负责各个页面的管理,创建和销毁其他进程
- 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
- 网络资源的管理,下载等
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
- GPU进程:最多一个,用于3D绘制等
- 浏览器内核(浏览器渲染进程,它内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为
2、浏览器内核中的多线程
- GUI渲染线程
- JS引擎线程:负责解析Javascript脚本,运行代码。
- 事件触发线程:归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中。
由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行) - 定时触发器线程:因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确。计时完毕后,添加到事件队列中,等待JS引擎空闲后执行。
- 异步http请求线程
三、setInterval缺点
定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。
setInterval(function, N) //即:每隔N秒把function事件推到消息队列中

上图可见,setInterval每隔100ms往队列中添加一个事件;100ms后,添加T1定时器代码至队列中,主线程中还有任务在执行,所以等待,some event执行结束后执行T1定时器代码;又过了100ms,T2定时器被添加到队列中,主线程还在执行T1代码,所以等待;又过了100ms,理论上又要往队列里推一个定时器代码,但由于此时T2还在队列中,所以T3不会被添加,结果就是此时被跳过;这里我们可以看到,T1定时器执行结束后马上执行了T2代码,所以并没有达到定时器的效果。
综上所述,setInterval有两个缺点:
使用setInterval时,某些间隔会被跳过;即使setInterval调用的方法报错了,他仍然会继续执行。无视网络延迟,可能多个定时器会连续执行;
可以这么理解:每个setTimeout产生的任务会直接push到任务队列中;而setInterval在每次把任务push到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中)。
因而我们一般用 setTimeout 模拟 setInterval,来规避掉上面的缺点。
模拟 setInterval() :
将 setTimeout() 包含在一个执行函数A中,而setTimeout() 自己的code执行函数又是A,然后在函数A外将函数A执行一次,即达到了循环执行的目的。
setTimeout(function () {
// 任务
setTimeout(arguments.callee, 1000);
}, 1000)
上述函数每次执行的时候都会创建一个新的定时器,第二个 setTimeout 使用了 arguments.callee() 获取当前函数的引用,并且为其设置另一个定时器。
好处:
在前一个定时器执行完前,不会向队列插入新的定时器(解决缺点一)- 保证定时器间隔(解决缺点二)
示例代码:
var intervalNum = 0;
function testsetInterval() {
var date = new Date();
console.log(date.getSeconds());
console.log("setInterval", intervalNum++);
}
function recursive() {
testsetInterval();
setTimeout(function () {
recursive() //递归,每隔4秒调用一次recursive()
}, 4000)
}
function testFuntion() {
recursive(); //在方法recursive外,调用一次recursive,以启动循环调用!
}

实现循环执行,和 setInterval() 功能相同。
当然也可以用 setInterval() 来模拟 setTimeout() ,具体使用那个,以具体的需求和场景具体分析,就像for循环可以模拟所有的循环一样(包括分支,以及do while一样)。一般情况下 setTimeout() 用于延迟执行某方法或功能;setInterval() 则一般用于刷新表单,对于一些表单的假实时指定时间刷新同步。
模拟 setTimeout() :
用 setInterval() 模拟 setTimeout() 很简单,在 setInterval() 执行一次后,立刻关闭窗口(当然这是耍无赖)或者执行 clearInterval() 方法(这个靠谱点)。clearInterval() 需要在 setInterval()执行code方法内或其他地方执行,不能紧接着 setInterval() 后面执行,那样setInterval() 还没等到执行,就已经被干掉了。
示例代码:
var intervalNum = 0, clearId = 0;
function testsetInterval(){
var date = new Date();
console.log(date.getSeconds());
console.log("setInterval", intervalNum++);
clearInterval(clearId); //也可以在此执行
}
function testFuntion() {
clearId = setInterval(function () {
testsetInterval(); //每隔4秒调用testsetInterval()
// clearInterval(clearId); //可以在此执行
},4000);
}

执行一次,关闭 setInterval(),和 setTimeout() 功能相同
最后,解释为什么 “ 建议传入函数而不是字符串以作为第一个参数”。
setTimeout()、setInterval() 允许传入一个JS代码字符串并执行,然而在JS代码中执行另一段JS代码时,代码首先会以正常的方式求值,然后在执行过程中对包含于字符串中的代码发起另一个求值运算,从而造成双重求值。它比直接包含的代码执行速度慢很多,原因在于, 每次调用setTimeout()、setInterval() 都会创建一个新的解释器/编译器实例。这必然使得代码执行速度变慢,效率降低,从而造成性能的浪费。所以建议传入函数而不是字符串来作为第一个参数。
实例
下面写了一个小例子:点击 start 开始旋转,点击 stop 停止旋转。
先是用 setInterval() 实现的:
<div class="divOne">
123
</div>
<button id="click">start</button>
<button id="stop">stop</button>
<script src="./js/jquery-1.8.3.min.js"></script>
<script src="./js/jquery.rotate.min.js"></script>
<script>
var angle = 0;
var myVar;
$('#click').on('click', function(){
myVar = setInterval(function(){ myTimer() }, 10);
})
function myTimer() {
angle += 3;
$('.divOne').rotate(angle);
}
$("#stop").on('click', function(){
clearInterval(myVar)
})
</script>
然后用 setTimeout 来模拟 setInterval 实现:
var angle = 0;
$('#click').on('click', function(){
setTimeout(myTimer, 10)
})
var myTimer = function() {
setTimeout(myTimer, 10)
angle += 3;
$('.divOne').rotate(angle);
}
$("#stop").on('click', function(){
console.log(111)
myTimer = null;
})
// 或者
var angle = 0;
var stop = false;
$('#click').on('click', function(){
stop = false;
setTimeout(function () {
angle += 3;
$('.divOne').rotate(angle);
var myTimer = setTimeout(arguments.callee, 10);
if( stop ){//符合条件 删除定时器
clearTimeout(myTimer);
}
}, 10);
})
$("#stop").on('click', function(){
console.log(111)
stop = true;
})





