17

我已经完成了一个 HTML 表单,它在许多不同的选项卡中有很多问题(来自数据库)。然后用户给出这些问题的答案。每次用户更改选项卡时,我的 Javascript 都会创建一个保存。问题是每次更改选项卡时我都必须遍历所有问题,并且每次都会将表单冻结约 5 秒钟。

我一直在寻找如何在后台运行保存功能的答案。显然没有真正的方法可以在后台运行某些东西,许多人建议使用setTimeout();例如这个How to get a group of js function running in background

但是这些例子都没有解释或考虑到即使我使用类似的东西setTimeout(saveFunction, 2000);也不能解决我的问题。在这种情况下,它只会推迟 2 秒。

有没有办法解决这个问题?

4

7 回答 7

11

显然没有真正的方法可以在后台运行某些东西......

大多数现代浏览器(但不是 IE9 和更早版本)上都有:Web Workers

但我认为您试图在错误的级别上解决问题:1. 应该可以在不到五秒的时间内循环通过所有控件,以及 2. 没有必要循环通过所有控件控制何时只有其中一个发生了变化。

我建议在尝试将处理卸载到后台之前查看这些问题。

例如,您可以拥有一个包含每个项目的当前值的对象,然后让每个项目的 UI 在值更改时更新该对象。然后,您将拥有该对象中的所有值,而无需再次遍历所有控件。

于 2013-09-19T12:42:10.390 回答
7

您可以使用网络工作者。这里的一些较旧的答案说它们没有得到广泛的支持(我猜在编写这些答案时没有得到广泛的支持),但今天它们得到了所有主要浏览器的支持

要运行 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 worker 既不能访问外部的 Javascript 变量、函数或类,也不能访问 DOM,但是你可以通过使用postMessage方法和onmessage事件来解决这个问题。在外部代码中,这些是 worker 对象的成员(worker在上面的示例中),而在 worker 内部,这些是全局上下文的成员(因此可以通过 usingthis或在前面没有任何内容的方式调用它们)。

postMessage并且onmessage双向工作,所以当worker.postMessage在外部代码中调用时,在工作人员中被解雇onmessage,在工作人员中postMessage被调用时,worker.onmessage在外部代码中被解雇。

postMessage接受一个参数,即您要传递的变量(但您可以通过传递数组来传递多个变量)。不幸的是,函数和 DOM 元素不能被传递,当你试图传递一个对象时,只会传递它的属性,而不是它的方法。

onmessage接受一个参数,它是一个MessageEvent对象。该MessageEvent对象有一个data属性,其中包含使用 的第一个参数发送的数据postMessage

这是一个使用网络工作者的例子。在这个例子中,我们有一个函数 ,functionThatTakesLongTime它接受一个参数并根据该参数返回一个值,我们希望使用 web worker 来在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).
于 2020-06-12T17:00:44.910 回答
2

你可以看看HTML5 web workers,虽然它们并没有得到广泛的支持。

于 2013-09-19T12:41:29.403 回答
1

这在后台工作:

setInterval(function(){ d=new Date();console.log(d.getTime()); }, 500);
于 2019-06-02T07:22:45.750 回答
0

如果你因为需要访问 DOM 而不能使用 web worker,你也可以使用async 函数。这个想法是创建一个refreshUI刷新 UI 的异步函数,然后在需要很长时间的函数中定期调用该函数。

refreshUI函数如下所示:

async function refreshUI(){
    await new Promise(r => setTimeout(r, 0));
}

一般来说,如果你放入await new Promise(r => setTimeout(r, ms));一个异步函数,它将运行该行之前的所有代码,然后等待ms几毫秒而不冻结 UI,然后继续运行该行之后的代码。有关更多信息,请参阅此答案

上面的refreshUI函数做了同样的事情,只是它在继续之前等待零毫秒而不冻结 UI,这实际上意味着它刷新 UI 然后继续。

如果您使用此功能经常刷新 UI,用户将不会注意到 UI 冻结。

虽然刷新 UI 需要时间(如果您只执行一次,则没有足够的时间注意到,但有足够的时间让您注意到是否在长 for 循环的每次迭代中都执行此操作)。因此,如果您希望函数在不冻结 UI 的情况下尽可能快地运行,则需要确保不要过于频繁地刷新 UI。因此,您需要在频繁刷新 UI 以使 UI 不死机和不频繁刷新 UI 之间找到平衡,因为这会使您的代码明显变慢。在我的用例中,我发现每 20 毫秒刷新一次 UI 是一个很好的平衡。

您可以使用从上面重写该refreshUI函数performance.now(),使其每 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()并且调用它的函数也必须是异步函数。

这是一个示例,它与我的其他答案末尾的示例执行相同的操作,但改用此方法:

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));
于 2020-08-29T10:44:31.047 回答
-1

这个库帮助我解决了你描述的一个非常相似的问题:https ://github.com/kmalakoff/background

它基本上是一个基于 WorkerQueue 库的顺序后台队列。

于 2013-09-19T12:43:55.037 回答
-5

只需创建一个隐藏按钮。将函数传递给它的 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>
于 2017-01-11T10:46:56.167 回答