我已经完成了一个HTML表单,它在很多不同的标签中都有很多问题(来自数据库)。然后用户在这些问题中给出答案。每次用户更改选项卡时,我的Javascript都会创建一个保存。问题是每次更改选项卡时我都必须遍历所有问题,并且每次冻结表单大约5秒钟。
我一直在寻找答案如何在后台运行我的保存功能。显然没有真正的方法可以在后台运行某些内容,很多人建议使用setTimeout();
例如这一个How to get a group of js function running in background
但这些例子都没有解释或考虑到即使我使用像setTimeout(saveFunction, 2000);
这样的东西也没有解决我的问题。在这种情况下,它只会将它推迟2秒。
有没有办法解决这个问题?
答案 0 :(得分:5)
显然没有真正的方法可以在背景上运行......
有most modern browsers(但不是IE9及更早版本):Web Workers。
但是我认为你试图在错误的级别上解决问题:1。应该可以在很多小于5秒的时间内遍历所有控件,并且2。当其中只有一个控件发生变化时,不应该必要循环遍历所有控件。
我建议在尝试将处理卸载到后台之前先查看这些问题。
例如,您可以拥有一个包含每个项目当前值的对象,然后让每个项目的UI在值更改时更新该对象。然后你就拥有了该对象中的所有值,而无需再次遍历所有控件。
答案 1 :(得分:3)
您可以使用web workers。这里的一些较早的答案说,并没有得到广泛的支持(我想这些答案写时还没有得到支持),但是今天they're supported by all major browsers。
要运行Web Worker,您需要创建内置Worker
类的实例。构造函数采用一个参数,该参数是javascript文件的URI,其中包含要在后台运行的代码。例如:
let worker = new Worker("/path/to/script.js");
Web worker遵循相同的原始策略,因此,如果您传递这样的路径,则目标脚本必须与调用它的页面在同一域中。
如果您不想为此创建新的Javascript文件,也可以使用数据URI:
let worker = new Worker(
`data:text/javascript,
//Enter Javascript code here
`
);
由于具有相同的原始策略,因此无法从数据URI发送AJAX请求,因此,如果需要在Web Worker中发送AJAX请求,则必须使用单独的Javascript文件。
您指定的代码(在单独的文件中或在数据URI中)将在调用Worker
构造函数后立即运行。
不幸的是,Web Workers既无权访问外部Javascript变量,函数或类,也无权访问DOM,但是您可以使用postMessage
方法和onmessage
事件来解决此问题。在外部代码中,它们是工作程序对象的成员(在上面的示例中为worker
),在内部工作程序中,它们是全局上下文的成员(因此可以使用this
来调用它们)或就这样,前面什么也没有)。
postMessage
和onmessage
两种方式都起作用,因此,在外部代码中调用worker.postMessage
时,将在工作线程中触发onmessage
,而当postMessage
在工作程序中被调用,worker.onmessage
在外部代码中被触发。
postMessage
接受一个参数,这是您要传递的变量(但是您可以通过传递数组来传递多个变量)。不幸的是,函数和DOM元素无法传递,并且当您尝试传递对象时,只会传递其属性,而不传递其方法。
onmessage
接受一个参数,它是MessageEvent
对象。 MessageEvent
对象具有data
属性,该属性包含使用postMessage
的第一个参数发送的数据。
这里是使用网络工作者的示例。在此示例中,我们有一个函数functionThatTakesLongTime
,该函数接受一个参数并根据该参数返回一个值,并且我们希望使用网络工作程序来查找functionThatTakesLongTime(foo)
而不会冻结UI,其中foo
是外部代码中的某些变量。
let worker = new Worker(
`data:text/javascript,
function functionThatTakesLongTime(someArgument){
//There are obviously faster ways to do this, I made this function slow on purpose just for the example.
for(let i = 0; i < 1000000000; i++){
someArgument++;
}
return someArgument;
}
onmessage = function(event){ //This will be called when worker.postMessage is called in the outside code.
let foo = event.data; //Get the argument that was passed from the outside code, in this case foo.
let result = functionThatTakesLongTime(foo); //Find the result. This will take long time but it doesn't matter since it's called in the worker.
postMessage(result); //Send the result to the outside code.
};
`
);
worker.onmessage = function(event){ //Get the result from the worker. This code will be called when postMessage is called in the worker.
alert("The result is " + event.data);
}
worker.postMessage(foo); //Send foo to the worker (here foo is just some variable that was defined somewhere previously).
答案 2 :(得分:1)
您可以查看HTML5 web workers,但它们并非广泛支持。
答案 3 :(得分:0)
这个库帮助我解决了你所描述的一个非常类似的问题:https://github.com/kmalakoff/background
它基本上是一个基于WorkerQueue库的顺序后台队列。
答案 4 :(得分:0)
只需创建一个隐藏按钮。将函数传递给它的onclick事件。 每当您想要调用该功能时(在后台),请拨打按钮的点击事件。
<html>
<body>
<button id="bgfoo" style="display:none;"></button>
<script>
function bgfoo()
{
var params = JSON.parse(event.target.innerHTML);
}
var params = {"params":"in JSON format"};
$("#bgfoo").html(JSON.stringify(params));
$("#bgfoo").click(bgfoo);
$("#bgfoo").click(bgfoo);
$("#bgfoo").click(bgfoo);
</script>
</body>
</html>
答案 5 :(得分:0)
这在后台工作:
setInterval(function(){ d=new Date();console.log(d.getTime()); }, 500);
答案 6 :(得分:-2)
如果由于需要访问DOM而无法使用网络工作者,则也可以使用async functions。这个想法是创建一个异步refreshUI
函数来刷新UI,然后在需要很长时间的函数中定期调用该函数。
refreshUI
函数如下所示:
async function refreshUI(){
await new Promise(r => setTimeout(r, 0));
}
通常,如果将await new Promise(r => setTimeout(r, ms));
放在异步函数中,它将在该行之前运行所有代码,然后等待ms
毫秒而不冻结UI,然后在此之后继续运行代码线。有关更多信息,请参见this answer。
上面的refreshUI
函数执行相同的操作,不同的是它在不冻结UI的情况下等待0毫秒 ,然后再继续操作,这实际上意味着先刷新UI,然后继续。
如果您使用此功能经常刷新用户界面,则用户不会注意到用户界面冻结。
尽管刷新UI需要花费时间(没有足够的时间让您注意到是否只执行一次,但是有足够的时间让您注意到是否在long for循环的每次迭代中都这样做)。因此,如果您希望函数在不冻结UI的情况下尽可能快地运行,则需要确保不要过于频繁地刷新UI。因此,您需要在刷新用户界面之间找到足够的平衡,以使用户界面不冻结,但又不经常刷新,这会使您的代码变慢。在我的用例中,我发现每20毫秒刷新一次UI是一个很好的平衡。
您可以使用performance.now()
从上方重写refreshUI
函数,以使其每20毫秒刷新一次UI(如果需要,您可以在自己的代码中调整该数字),无论多久一次你叫它:
let startTime = performance.now();
async function refreshUI(){
if(performance.now() > startTime + 20){ //You can change the 20 to how often you want to refresh the UI in milliseconds
startTime = performance.now();
await new Promise(r => setTimeout(r, 0));
}
}
如果执行此操作,则不必担心经常调用refreshUI
(但是您仍然需要确保足够频繁地调用它)。
由于refreshUI
是一个异步函数,因此您需要使用await refreshUI()
来调用它,并且调用它的函数也必须是一个异步函数。
以下示例与my other answer末尾的示例具有相同的作用,但改用以下方法:
let startTime = performance.now();
async function refreshUI(){
if(performance.now() > startTime + 20){ //You can change the 20 to how often you want to refresh the UI in milliseconds
startTime = performance.now();
await new Promise(r => setTimeout(r, 0));
}
}
async function functionThatTakesLongTime(someArgument){
//There are obviously faster ways to do this, I made this function slow on purpose just for the example.
for(let i = 0; i < 1000000000; i++){
someArgument++;
await refreshUI(); //Refresh the UI if needed
}
return someArgument;
}
alert("The result is " + await functionThatTakesLongTime(3));