setTimeout,它就是一个定时器,用来指定某个函数在多少毫秒以后执行。前端
var timeoutID1 = setTimeout(function[, delay, arg1, arg2, ...]);
var timeoutID2 = setTimeout(function[, delay]);
var timeoutID3 = setTimeout(code[, delay]);
复制代码
alert('test')
,此法不建议使用)为延迟毫秒数
,可选的,默认值为0.浏览器渲染进程中全部运行在主线程上的任务都须要先添加到消息队列,而后事件循环系统再按照顺序执行消息队列中的任务。web
在 Chrome 中除了正常使用的消息队列以外,还有另一个消息队列(咱们能够称为延迟队列
),这个队列中维护了须要延迟执行的任务列表,包括了定时器和 Chromium 内部一些须要延迟执行的任务。因此当经过 JavaScript 建立一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。浏览器
好比这样的一段代码:微信
function foo(){
console.log("test")
}
var timeoutID = setTimeout(foo,100);
复制代码
当经过 JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程将会建立一个回调任务,包含了回调函数foo、当前发起时间、延迟执行时间等,其模拟代码以下所示:dom
struct DelayTask{
int64 id;
CallBackFunction cbf;
int start_time;
int delay_time;
};
DelayTask timerTask;
timerTask.cbf = foo;
timerTask.start_time = getCurrentTime(); //获取当前时间
timerTask.delay_time = 100;//设置延迟执行时间
复制代码
建立好回调任务以后,就会将该任务添加到延迟执行队列中。那这个回调任务,何时会被执行呢? 浏览器中有个函数是专门用来处理延迟执行任务的,暂且称为ProcessDelayTask
,它的主要逻辑以下:ide
void ProcessTimerTask(){
//从delayed_incoming_queue中取出已经到期的定时器任务
//依次执行这些任务
}
TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainTherad(){
for(;;){
//执行消息队列中的任务
Task task = task_queue.takeTask();
ProcessTask(task);
//执行延迟队列中的任务
ProcessDelayTask()
if(!keep_running) //若是设置了退出标志,那么直接退出线程循环
break;
}
}
复制代码
其实就是,当浏览器处理完消息队列中的一个任务以后,就会开始执行 ProcessDelayTask 函数。ProcessDelayTask 函数会根据发起时间和延迟时间计算出到期的任务,而后依次执行这些到期的任务。等到期的任务执行完成以后,再继续下一个循环过程。这样定时器就实现了,从这个过程也能够明显看出,定时器并不必定是准时延后执行的
。函数
在使用 setTimeout 的时候,有不少因素会致使回调函数执行比设定的预期值要久,其中一个就是上文说到的,若是处理的当前任务耗时过长,定时器设置的任务就会被延后执行。 好比在浏览器中执行这样一段代码,并打印执行时间:oop
function bar() {
console.log('bar')
const endTime = Date.now()
console.log('cost time',endTime - startTime)
}
function foo() {
setTimeout(bar, 0);
for (let i = 0; i < 5000; i++) {
let i = 5+8+8+8
console.log(i)
}
}
foo()
复制代码
执行结果如图: 学习
var name= 1;
var MyObj = {
name: 2,
test:1,
showName: function(){
console.log(this.name,this.test);
}
}
setTimeout(MyObj.showName,1000)
MyObj.showName()
//先输出 2 1
// 1s后输出 1 undefined
复制代码
这里其实认真分析一下,也很好理解这个 this 的指向。按照 this 的规定,若是是对象调用(obj.fn()
),那么this指向该对象,所以MyObj.showName()
输出的是 MyObj 里面的值。在 setTimeout 中,入参是MyObj.showName,这里是把这个值传了进去,能够理解为:测试
const fn = MyObj.showName
setTimeout(fn,1000)
复制代码
这样看,在setTimeout里面,当执行到的时候,实际上就是在window
下执行fn,此时的this,就指向了window,而不是原来的函数。
若是 setTimeout 存在嵌套调用,调用超过5次后,系统会设置最短执行时间间隔为 4 毫秒。 咱们能够在浏览器粗略测试一下,有以下代码:
let startTime = Date.now()
function cb() {
const endTime = Date.now()
console.log('cost time',endTime - startTime)
startTime = startTime
setTimeout(cb, 0);
}
setTimeout(cb, 0);
复制代码
执行结果:
从结果能够看出,前面五次调用的时间间隔比较小,嵌套调用超过五次以上,后面每次的调用最小时间间隔是 4 毫秒(我运行的结果,间隔基本是 5ms,考虑有代码执行的计算偏差)。
之因此出现这样的状况,是由于在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,若是定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒。能够看下源码(https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/frame/dom_timer.cc):
static const int kMaxTimerNestingLevel = 5;
// Chromium uses a minimum timer interval of 4ms. We'd like to go
// lower; however, there are poorly coded websites out there which do
// create CPU-spinning loops. Using 4ms prevents the CPU from
// spinning too busily and provides a balance between CPU spinning and
// the smallest possible interval timer.
static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4);
复制代码
因此,一些实时性较高的需求就不太适合使用 setTimeout 了,好比你用 setTimeout 来实现 JavaScript 动画就不必定是一个很好的主意。
若是标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及下降耗电量。这一点你在使用定时器的时候要注意。
Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,若是 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,这致使定时器会被当即执行。如:
let startTime = Date.now()
function foo(){
const endTime = Date.now()
console.log('cost time',endTime - startTime)
console.log("test")
}
var timerID = setTimeout(foo,2147483648);//会被当即调用执行
复制代码
执行结果:
运行后能够看到,这段代码是当即被执行的。但若是将延时值修改成小于 2147483647 毫秒的某个值,那么执行时就没有问题了。