常常使用Javascript的同窗必定对setInterval
很是熟悉,当使用setInterval(callback, timer)
时,每通过timer毫秒时间,系统都将调用一次callback。请问全局若是没有提供setInterval函数,该如何本身实现这一功能?javascript
最简单的思路即是经过简单的循环或者递归,每次检查时间戳是否已经超过上次触发给定函数的时间加上间隔时间,若是已经超过便再次触发函数,并重置计时器至当前时间。java
const setInterval1 = (func, interval) => { let startTime = Date.now(); const config = { shouldStop: false }; while (!config.shouldStop) { if (Date.now() - startTime >= interval) { func(); startTime = Date.now(); } } return config; } const myClearInterval = config => { config.shouldStop = true; }
然而这样的解法有一个致命问题,咱们将setInterval1变成一个阻塞函数,主线程会卡死在这个无限循环或者递归中,致使以后的代码或者事件没法执行。想了解详细缘由的请戳: 并发模型与事件循环,JavaScript:完全理解同步、异步和事件循环(Event Loop)node
setTimeout的好处在于,它是在消息队列里面添加一个待执行的消息,因此并不会堵塞主线程。更方便的在于,因为setTimeout自带定时器功能,咱们甚至不用本身去维护一个时间戳。咱们能够经过不断递归调用setTimeout来实现setInterval的效果web
const setInterval2 = (func, interval) => { const config = { shouldStop: false } const loop = () => { if (!config.shouldStop) { func(); setTimeout(loop, interval); } } setTimeout(loop, interval); return config; } const myClearInterval = config => { config.shouldStop = true; }
然而使用setTimeout
有违这道题的初衷,由于setTimeout
在本质上和setInterval
是相似的,多少有些做弊的嫌疑。那有没有别的非阻塞方案呢?在浏览器环境中,咱们有requestAnimationFrame()
,而在nodejs环境中,咱们有setImmediate()
。以requestAnimationFrame为例,这将保证咱们的代码只会在每一帧render以前被递归一次,从而避免了阻塞其余代码。segmentfault
const setInterval3 = (func, interval) => { let startTime = Date.now(); const config = { shouldStop: false } const check = () => { if (!config.shouldStop) { if (Date.now() - startTime > interval) { func(); startTime = Date.now(); } if(typeof window === 'undefined') { setImmediate(check); } else { window.requestAnimationFrame(check) } } } check(); return config; } const myClearInterval = config => { config.shouldStop = true; }
requestAnimationFrame能确保咱们在每帧显示前被调用一次,从而检计时器是否到期,可是若是被执行的函数计算量极大,致使帧内没法完成时,该如何保证给定函数能按时执行呢?显然,此时只依靠主线程来确保计时程序和给定程序都能准确执行,有点困难,可是若是将计时程序放入另外一线程中,而主程序只负责监听定时器事件和执行给定程序,是否是会好一些呢?因此咱们这里利用浏览器提供的Web Worker API来实现多线程。请注意这里因为没有调用另外一个脚本,咱们经过blob和object url的方式将咱们的定时器程序check
传入Web Worker中。浏览器
const setInterval4 = (func, interval) => { if (typeof window !== 'undefined' && window.Worker && window.Blob) { const check = new Blob(["(", function(){ self.onmessage = function(e) { const interval = e.data; let startTime = Date.now(); while(true) { if (Date.now() - startTime >= interval) { startTime = Date.now(); self.postMessage(Date.now()); } } } }.toString(), ")()"], { type: "text/javascript" }); const worker = new Worker(window.URL.createObjectURL(check)); worker.onmessage = func; worker.postMessage(interval); return worker; } else { console.log('Your environment is not supported'); } } const myClearInterval = config => { config.terminate() }