我有一个网页,其中函数中的 javascript 计算需要大量时间才能完成并使页面冻结。当计算在后台发生时,我应该使用什么技术来确保 javascript 不会冻结浏览器?
5 回答
如果您只需要进行计算并且在长时间运行的计算过程中不需要访问 DOM,那么您有两种选择:
- 您可以将计算分解为多个部分,并在
setTimeout()
. 在每次setTimeout()
调用时,浏览器都可以自由地处理其他事件,并使页面保持活跃和响应。当您完成计算的最后一部分时,您可以执行结果。 - 您可以在现代浏览器中使用网络工作者在后台运行计算。在 webworker 中完成计算后,它会向主线程发送一条消息,然后您可以使用结果更新 DOM。
这是一个相关的答案,也显示了一个示例:Best way to iterate over an array without blocking the UI
让我通过一个具体的精简示例来详细说明@jfriend00 的答案。这是一个长时间运行的 JavaScript 进程,可以通过单击按钮来启动。一旦它运行,它会冻结浏览器。该过程由一个长循环组成,该循环重复一些工作负载,其中一次迭代花费的时间相对较短。
由于浏览器冻结,调试这样的脚本并不容易。避免浏览器冻结的一种替代方法是使用网络工作者。这种方法的缺点是 web worker 本身的可调试性很差:不支持像 Firebug 这样的工具。
<html>
<head>
<script>
var Process = function(start) {
this.start = start;
}
Process.prototype.run = function(stop) {
// Long-running loop
for (var i = this.start; i < stop; i++) {
// Inside the loop there is some workload which
// is the code that is to be debugged
console.log(i);
}
}
var p = new Process(100);
window.onload = function() {
document.getElementById("start").onclick = function() {
p.run(1000000000);
}
}
</script>
</head>
<body>
<input id="start" type="button" value="Start" />
</body>
</html>
使用 Queue 数据结构(例如http://code.stephenmorley.org/javascript/queues/)、间隔计时器和对原始流程的控制流的一些小修改,可以构建一个不会冻结浏览器的 GUI ,使过程完全可调试,甚至允许其他功能,如步进、暂停和停止。
这是怎么回事:
<html>
<head>
<script src="http://code.stephenmorley.org/javascript/queues/Queue.js"></script>
<script>
// The GUI controlling process execution
var Gui = function(start) {
this.timer = null; // timer to check for inputs and/or commands for the process
this.carryOn = false; // used to start/pause/stop process execution
this.cmdQueue = new Queue(); // data structure that holds the commands
this.p = null; // process instance
this.start = start;
this.i = start; // input to the modified process
}
Gui.prototype = {
/**
* Receives a command and initiates the corresponding action
*/
executeCmd: function(cmd) {
switch (cmd.action) {
case "initialize":
this.p = new Process(this);
break;
case "process":
this.p.run(cmd.i);
break;
}
},
/*
* Places next command into the command queue
*/
nextInput: function() {
this.cmdQueue.enqueue({
action: "process",
i: this.i++
});
}
}
// The modified loop-like process
var Process = function(gui) {
this.gui = gui;
}
Process.prototype.run = function(i) {
// The workload from the original process above
console.log(i);
// The loop itself is controlled by the GUI
if (this.gui.carryOn) {
this.gui.nextInput();
}
}
// Event handlers for GUI interaction
window.onload = function() {
var gui = new Gui(100);
document.getElementById("init").onclick = function() {
gui.cmdQueue.enqueue({ // first command will instantiate the process
action: "initialize"
});
// Periodically check the command queue for commands
gui.timer = setInterval(function() {
if (gui.cmdQueue.peek() !== undefined) {
gui.executeCmd(gui.cmdQueue.dequeue());
}
}, 4);
}
document.getElementById("step").onclick = function() {
gui.carryOn = false; // execute just one step
gui.nextInput();
}
document.getElementById("run").onclick = function() {
gui.carryOn = true; // (restart) and execute until further notice
gui.nextInput();
}
document.getElementById("pause").onclick = function() {
gui.carryOn = false; // pause execution
}
document.getElementById("stop").onclick = function() {
gui.carryOn = false; // stop execution and clean up
gui.i = gui.start;
clearInterval(gui.timer)
while (gui.cmdQueue.peek()) {
gui.cmdQueue.dequeue();
}
}
}
</script>
</head>
<body>
<input id="init" type="button" value="Init" />
<input id="step" type="button" value="Step" />
<input id="run" type="button" value="Run" />
<input id="pause" type="button" value="Pause" />
<input id="stop" type="button" value="Stop" />
</body>
</html>
虽然这种方法当然不适合人们能想到的所有长时间运行的脚本,但它肯定可以适应任何类似循环的场景。我正在使用它将Numenta 的 HTM/CLA人工智能算法移植到浏览器。
有些浏览器只有一个线程来运行代码和更新 UI(换句话说,在计算完成之前,浏览器会显示为“冻结”)。您会想尝试以一种或另一种方式异步执行操作。
如果计算真的很贵,你可能想调用服务器,让服务器计算,计算完成后回调客户端。
如果计算有点昂贵,您可以尝试在客户端上分块进行。这实际上不是异步的(因为客户端会在执行每个块时阻塞),但目标是使块足够小,以至于阻塞不明显。
setTimeout(function() { ..code }, 0);
我推荐这个用于繁重的执行时间,也可以尝试添加加载 ajax
$(window).on("load", function (e) { }); // for jquery v3
如果它在加载过程中。
我认为这应该可以解决您的问题,
function myClickOperation(){
var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
setTimeout(function () { btn_savebutton2.click() }, 1000);
}
// 完整的 Html 内容
<html>
<script>
function myClickOperation(){
var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
document.getElementById('savebutton1').disabled = true;
setTimeout(function () { btn_savebutton2.click() }, 1000);
}
function testClick(){
var idd = document.getElementById("myid");
idd.innerHTML =idd.innerHTML +"<br/>" + new Date();
if(true){
setTimeout(function () { testClick() }, 1);
}
}
</script>
<body>
<input type="button" id="savebutton1" onclick="myClickOperation()" value="Click me" />
<input type="button" id="savebutton2" onclick="testClick()" value="Do not click this" />
<input type="text"/>
<input type="button" value="temp"/>
<div style="height: 300px;overflow-y: scroll;" id="myid"/>
</body>