我正在使用
这个测试来测试
setTimeout
的准确性。现在我注意到(正如预期的那样)setTimeout
不是很准确,但对于大多数设备来说并不是非常不准确。现在,如果我在 Chrome 中运行测试并让它在后台选项卡中运行(因此,切换到另一个选项卡并在那里浏览),返回测试并检查结果(如果测试完成),它们会发生巨大的变化。看起来超时运行得慢了很多。在 FF4 或 IE9 中测试,没有发生这种情况。
所以看起来 Chrome 会暂停或至少减慢没有焦点的选项卡中的 JavaScript 执行速度。在互联网上找不到太多有关该主题的信息。这意味着我们无法运行后台任务,例如使用 XHR 调用和
setInterval
定期检查服务器(我怀疑 setInterval
会看到相同的行为,如果有时间的话,我会编写一个测试) .
有人遇到过这种情况吗?对于这种暂停/减速是否有解决方法?你会称其为错误吗?我应该将其归档吗?
我最近问过这个问题,这是设计行为。当选项卡处于非活动状态时,每秒最多只能调用该函数一次。这是代码更改。
也许这会有所帮助: 当 Chrome 中的选项卡处于非活动状态时,如何使 setInterval 也能工作?
TL;博士:使用Web Workers。
有一个使用 Web Workers 的解决方案,因为它们在单独的进程中运行并且不会减慢
我编写了一个小脚本,无需更改代码即可使用 - 它只是覆盖函数 setTimeout、clearTimeout、setInterval、clearInterval
只需将其包含在所有代码之前
播放空声音会强制浏览器保留性能。我在阅读此评论后发现了它:How to make JavaScript run at normal speed in Chrome Even while tab is not active?
该评论的来源在这里:
Chromium 内部人士还澄清,对于所有“播放音频”的后台选项卡以及任何“存在活动 Websocket 连接”的页面,将自动禁用积极的节流。
对于使用 WebSocket 的浏览器游戏,我需要无限的性能点播,所以我从经验中知道,使用 WebSocket 并不能确保无限的性能,但从测试来看,播放音频文件似乎可以确保它
这是我为此目的创建的两个空音频循环,您可以在商业上自由使用它们: http://adventure.land/sounds/loops/empty_loop_for_js_performance.ogg http://adventure.land/sounds/loops/empty_loop_for_js_performance.wav
(它们包括-58db噪音,-60db不起作用)
我根据用户需求使用 Howler.js 来玩它们:https://github.com/goldfire/howler.js
function performance_trick()
{
if(sounds.empty) return sounds.empty.play();
sounds.empty = new Howl({
src: ['/sounds/loops/empty_loop_for_js_performance.ogg','/sounds/loops/empty_loop_for_js_performance.wav'],
volume:0.5,
autoplay: true, loop: true,
});
}
令人遗憾的是,默认情况下没有内置方法可以打开/关闭完整的 JavaScript 性能,但是,加密矿工可以使用 Web Workers 劫持您的所有计算线程,而无需任何提示。
对于单元测试,您可以使用参数运行 chrome/chromium:
--disable-background-timer-throttling
我发布了 worker-interval npm 包,其中 setInterval 和clearInterval 实现使用 Web-Workers 在 Chrome、Firefox 和 IE 的非活动选项卡上保持和运行。
大多数现代浏览器(Chrome、Firefox 和 IE)的间隔(窗口计时器)被限制为在非活动选项卡中每秒触发一次。
您可以找到更多信息
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval
我将 jQuery 核心更新到 1.9.1,它解决了非活动选项卡中的间隔差异。我会先尝试一下,然后再研究其他代码覆盖选项。
浏览器旨在优化用户体验和电池寿命,它们限制后台选项卡的活动以节省 CPU 和电量。
躲避后台执行抑制的2种方法是 (ping/pong 消息示例):
第一 -
内联:
// Web Worker code defined as a string
const workerCode = `
// When the worker receives a message...
onmessage = function(e) {
// ...if that message is 'start'
if (e.data === 'start') {
// ...set an interval to send a heartbeat every minute
setInterval(() => {
// Fetch a heartbeat from the server
fetch('http://127.0.0.1:9000/heartbeat')
.then(response => {
// If the response isn't okay, throw an error
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Log the received heartbeat
console.log('Heartbeat received:', data);
})
.catch(error => {
// If there's an error, log it
console.error('Fetch error:', error.message);
});
}, 60000);
}
};
`;
// Create a new Blob object from the worker code
const blob = new Blob([workerCode], { type: 'application/javascript' });
// Create a new Web Worker from the blob URL
const worker = new Worker(URL.createObjectURL(blob));
// Post a 'start' message to the worker to begin the heartbeat
worker.postMessage('start');
第二-
worker.js 文件:
// When the worker receives a message...
onmessage = function(e) {
// ...if that message is 'start'
if (e.data === 'start') {
// ...set an interval to send a heartbeat every minute
setInterval(() => {
// Fetch a heartbeat from the server
fetch('http://127.0.0.1:9000/heartbeat')
.then(response => {
// If the response isn't okay, throw an error
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Log the received heartbeat
console.log('Heartbeat received:', data);
})
.catch(error => {
// If there's an error, log it
console.error('Fetch error:', error.message);
});
}, 60000);
}
};
主要部分:
// Create a new Web Worker from the external worker file
const worker = new Worker('worker.js');
// Post a 'start' message to the worker to begin the heartbeat
worker.postMessage('start');
这是我的解决方案,它获取当前毫秒,并将其与创建函数的毫秒进行比较。对于间隔,它将在运行该函数时更新毫秒。您还可以通过 id 获取间隔/超时。
<script>
var nowMillisTimeout = [];
var timeout = [];
var nowMillisInterval = [];
var interval = [];
function getCurrentMillis(){
var d = new Date();
var now = d.getHours()+""+d.getMinutes()+""+d.getSeconds()+""+d.getMilliseconds();
return now;
}
function setAccurateTimeout(callbackfunction, millis, id=0){
nowMillisTimeout[id] = getCurrentMillis();
timeout[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisTimeout[id] + +millis)){callbackfunction.call(); clearInterval(timeout[id]);} }, 10);
}
function setAccurateInterval(callbackfunction, millis, id=0){
nowMillisInterval[id] = getCurrentMillis();
interval[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisInterval[id] + +millis)){callbackfunction.call(); nowMillisInterval[id] = getCurrentMillis();} }, 10);
}
//usage
setAccurateTimeout(function(){ console.log('test timeout'); }, 1000, 1);
setAccurateInterval(function(){ console.log('test interval'); }, 1000, 1);
</script>