这是一个更新,并解释了为什么先前答案(现已删除)中的代码是错误的。
首先,让我们重申一下问题:允许阶乘函数在 IE 中运行,而不会触发“长时间运行脚本”警告。
这是之前提出的代码:
BROKEN. DO NOT USE.
var executions = 0;
function factorial(x) {
executions++;
if (x > 1) {
if (executions % 100 === 0) {
return (function() { // NO NO NO
var y = x;
setTimeout(function(y) { return y*factorial(y-1); }, 50);
})();
} else {
return x*factorial(x-1);
}
} else {
return 1;
}
}
好的,那么该代码有什么问题?
阶乘函数本身是递归的。这意味着函数调用自身。每次函数调用自身时,都会在内存中获得另一个堆栈帧。上面的代码试图做的是一百次调用自己。我不知道浏览器可以容忍多少嵌套堆栈帧,但 100 似乎有点高。我会分10批做。
上面提出的函数不是异步的。当您使用 setTimeout() 绕过 IE 警告时,该函数需要变为异步的。这意味着 -var value = func(x);
您需要转换代码以传递回调,而不是像 那样调用它,异步函数在获得结果时会调用该回调。
与上述问题相关,建议代码中使用 setTimeout 是错误的。在一个地方,代码是这样做的:
return (function() { // NO NO NO
var y = x;
setTimeout(function(y) { return y*factorial(y-1); }, 50);
})();
那有什么作用?让我们分解一下。它有一个匿名函数。该函数被调用(通过最后的开闭括号)。该调用的值由主阶乘函数返回。调用 anon 函数的价值是什么?问题a:它没有返回值。它是未定义的。(请参阅在没有 return 语句的情况下,javascript 函数会返回什么?)
修复它并不像返回调用的值那么简单setTimeout()
。那也是错误的。的返回值setTimeout()
是一个标识符,可用于清除超时clearTimeout()
。 它绝对不是 setTimeout 调用所传递的值。
好的,如何解决?首先,实现阶乘函数将是异步的,因此获取和显示结果将如下所示:
function displayFacResult(result) {
var t2 = document.getElementById('t2');
t2.value = result;
}
function b1_Click() {
var t1 = document.getElementById('t1'),
value = parseInt(t1.value, 10);
computeFactorialAsynchronously(value, displayFacResult);
}
单击按钮调用“计算”,并将结果调用的函数的名称传递给它。使用结果调用的函数实际上进行了显示。这是异步调用模式。
好的,现在计算。
function computeFactorialAsynchronously(firstX, callback) {
var batchSize = 3, limit=0, result = 1;
var doOneFactorialStep = function(x, steps) {
if (steps) { limit = x - steps; }
if (x==1) { callback(result); }
if (x>limit) {
result *= x;
doOneFactorialStep(x-1);
}
else {
setTimeout(function () {
doOneFactorialStep(x, batchSize);
}, 1);
}
};
doOneFactorialStep(firstX, batchSize);
// the actual return value of the computation
// always comes in the callback.
return null;
}
它通过“chunk”计算阶乘,每个chunk涉及N次乘法,并由上面的变量“steps”表示。N (steps) 的值决定了递归的级别。100 可能太大了。3 可能对于良好的性能来说太小了,但它说明了异步性。
在 computeFactorialAsynchronously 函数内部,有一个辅助函数计算一个块,然后调用 setTimeout 来计算下一个块。有一些简单的算法可以管理何时停止计算当前块。
工作示例:http: //jsbin.com/episip
从某种意义上说,转向异步模型会让你远离纯粹的函数式隐喻,其中计算的结果就是函数的结果。我们可以在 javascript 中执行此操作,但它会遇到 IE 的“长时间运行脚本”警告。为了避免警告,我们采用异步方式,这意味着“computeFactorial”的返回值不是实际的阶乘。在异步模型中,我们通过“副作用”获得结果——来自计算函数在完成计算时调用的回调函数。