比如说,我写了一个5秒的定时器或延时器,js和浏览器是怎么控制这个时间的,也就是为什么5秒钟以后才执行,它的内部是怎么实现的,为什么5秒钟之前不会执行,这个时间是怎么控制的,特别想知道这个问题,网上也找不到相应的答案
比如说,我写了一个5秒的定时器或延时器,js和浏览器是怎么控制这个时间的,也就是为什么5秒钟以后才执行,它的内部是怎么实现的,为什么5秒钟之前不会执行,这个时间是怎么控制的,特别想知道这个问题,网上也找不到相应的答案
是定时器搞得鬼。JavaScript引擎都有一个定时器timer,当调用setTimeout
时,JS引擎会在设定的ms后将传入的函数放入事件队列,排队等待主线程调用。
这里涉及的知识包括线程、同步、异步、事和件循环。由于题主关注的问题在于如何控制延迟时间,所以这些问题不在赘述,不过这些知识点值得深入学习。
简单理解的话:
js是单线程的,定时器里面的方法(以及其他异步方法,包括ajax回调,promise的then)会放入事件栈,只有当线程里面的代码全部执行完之后才会去从事件栈中取出相应达到执行条件的方法放入处理线程中,处理完后再去事件栈中取,不断循环。因此实际上延迟的5秒并不是准确的时间,它只是在处理完当前线程里的事务(有可能6秒才处理完)再从事件栈中取(判断是否已经超过了5秒)
我是不知道v8引擎是怎么实现的,要知道具体细节得去看文档和源码。本来想写在评论里但感觉我要说的应该还有点参考价值emmm
我能想到的,应该也是唯一的实现方法就是轮询+忙等待,以下是我的两个猜想
JavaScript的runtime单独开一个不停计时Timer线程,它保持与系统时间同步,每次用户调用setTimeout
或setInerval
时,就将任务及其定时信息放到一个列表中,例如:[ timeout任务:时间为123毫秒时执行, interval任务:时间为456毫秒时执行, ... ]
,然后无限遍历这个任务列表(轮询),将每个任务计划的执行时间与当前系统时间对比,时间到了的就把这个任务的回调函数丢到主线程的执行队列中,并根据任务类型(timeout或interval)删除这个任务或设置任务的下一次执行时间。
第二个猜想我觉得会更合理一些,和第一个的区别就是不单独为Timer开一个线程,而是主线程每趟event loop都执行一次Timer的滴答函数,每次都遍历所有任务,将到时间的任务丢到执行队列中去。相当于这样
(function (global) {
class Timer {
constructor() {
this.schedule = []
this.timeoutHandler = 1
this.intervalHandler = 1
this.tick()
}
tick() {
const now = Date.now()
let nextSchedule = []
for(let task of this.schedule) {
if(task.time >= now) {
// Timeout!
if(task.type == 'timeout') {
setImmediate(task.fn)
} else if(task.type == 'interval') {
setImmediate(task.fn)
task.time += task.interval
nextSchedule.push(task) // Schedule next execution
}
} else nextSchedule.push(task) // Not yet
}
this.schedule = nextSchedule
setImmediate(() => this.tick())
}
setTimeout(fn, time) {
this.schedule.push({
fn,
type: 'timeout',
time: Date.now() + time,
handler: this.timeoutHandler
})
return this.timeoutHandler++
}
setInterval(fn, interval) {
this.schedule.push({
fn,
interval,
type: 'interval',
time: Date.now() + interval,
handler: this.intervalHandler
})
return this.intervalHandler++
}
clearTimeout(handler) { ... }
clearInterval(handler) { ... }
}
const timer = new Timer()
global.setTimeout = function setTimeout(fn, time) { return timer.setTimeout(fn, time) }
global.setInerval = function setInterval(fn, interval) { return timer.setInterval(fn, interval) }
...
})(window)
浏览器引擎有JS引擎,毫无疑问专门解析JS代码的,同时浏览器还有属于自己的时间模块,我喜欢这个叫至于你也可以理解为别的方面,而setTimeout与setInterval都隶属于这时间模块,而且最重要的是 定时器属于异步的 也就是只有同步的代码完成后才会执行异步的代码 这就是所谓的线程空余时,事件循环。 所以兄弟,如果非要说底层 希望这个回答对你有所帮助,当然这也是我自己的理解
10 回答11.1k 阅读
6 回答3k 阅读
5 回答4.8k 阅读✓ 已解决
4 回答3.1k 阅读✓ 已解决
2 回答2.6k 阅读✓ 已解决
3 回答5.1k 阅读✓ 已解决
3 回答1.8k 阅读✓ 已解决
个人理解,仅供参考:
js是单线程模式,按理说就是按顺序来执行代码块,放到任务队列中去执行,所以是存在阻塞这个概念的,也就是说上一个代码段执行完了才会执行下一个。但是定时器以及Ajax之类是异步执行,似乎有点多线程的感觉了,但实际上在JS中还是单线程,不过浏览器是有多个线程的。所以定时器在设定以后,也会放入任务队列中去,但是是在你设定的延迟时间后才执行。至于楼主说的定时器的工作原理,应该是比较底层的东西,才学浅薄,不太了解,不好意思。楼主可以参考下面这个图,js引擎线中的执行顺序。