setTimeout,它就是一个定时器,用来指定某个函数在多少毫秒以后执行。前端
setTimeout用法web
var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]); var timeoutID = setTimeout(function[, delay]); var timeoutID = setTimeout(code[, delay]);
浏览器渲染进程中全部运行在主线程上的任务都须要先添加到消息队列,而后事件循环系统再按照顺序执行消息队列中的任务。浏览器
在 Chrome 中除了正常使用的消息队列以外,还有另一个消息队列(咱们能够称为延迟队列),这个队列中维护了须要延迟执行的任务列表,包括了定时器和 Chromium 内部一些须要延迟执行的任务。因此当经过 JavaScript 建立一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。微信
好比这样的一段代码:markdown
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 函数会根据发起时间和延迟时间计算出到期的任务,而后依次执行这些到期的任务。等到期的任务执行完成以后,再继续下一个循环过程。这样定时器就实现了,从这个过程也能够明显看出,定时器并不必定是准时延后执行的。函数
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()
执行结果如图:oop
从结果能够看到,执行 foo 函数所消耗的时长是 365 毫秒,这也就意味着经过 setTimeout 设置的任务被推迟了 365 毫秒才执行,而设置 setTimeout 的回调延迟时间是 0。学习
好比这段代码: 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,而不是原来的函数。
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 动画就不必定是一个很好的主意。
未激活的页面,setTimeout 执行最小间隔是 1000 毫秒
若是标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及下降耗电量。这一点你在使用定时器的时候要注意。
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 毫秒的某个值,那么执行时就没有问题了。