DVK*_*DVK 8 html javascript firefox cross-browser settimeout
我有以下代码,它演示了直接从事件触发器调用长时间运行的函数与使用的区别setTimeout().
预期行为:
当按下第一个按钮时,它会显示为按下,计算运行几秒钟,然后当计算完成时,按钮再次按下,第二列从"尚未计算"变为"计算完成".(我不会详细说明为什么会发生这种情况; 它在相关答案中进行了解释.)
按下第二个按钮时,按钮立即按下; 第二列立即更改为"正在计算..."文本.当计算在几秒钟后完成时,第二列从"计算..."变为"完成计算".
实际发生了什么:
这在Chrome中完美运行(两个按钮都按预期运行)
这在Internet Explorer 8中完美运行
这在Firefox(第25版)中不起作用.具体来说,第二个按钮作为第一个按钮表现为100%.
在更改超时setTimeout()从0到1无影响
在更改超时setTimeout()从0以500 作品
这让我有一个很大的难题.
根据为什么setTimeout()有效的原因,而缺少一个没有的原因,延迟对事物的运作方式应该没有影响,因为这里setTimeout()的主要目的是改变排队顺序,而不是拖延事情.
那么,为什么它不能在Firefox上使用延迟0或1,但按预期工作延迟500(并且适用于Internet Explorer 8/Chrome上的任何延迟)?
更新:除了下面的源代码,我还做了一个JSFiddle.但由于某些原因,JSFiddle甚至拒绝加载我的Internet Explorer 8,因此对于该测试,需要以下代码.
更新2:有人提出了dom.min_timeout_valueFirefox中配置设置问题的可能性.我已将其编辑为4到0,重新启动浏览器,并且没有修复任何内容.它仍然失败,超时为0或1,成功为500.
这是我的源代码 - 我只是将其保存到C:驱动器上的HTML文件中,并在所有三个浏览器中打开:
<html><body>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<table border=1>
<tr><td><button id='do'>Do long calc - bad status!</button></td>
<td><div id='status'>Not Calculating yet.</div></td></tr>
<tr><td><button id='do_ok'>Do long calc - good status!</button></td>
<td><div id='status_ok'>Not Calculating yet.</div></td></tr>
</table>
<script>
function long_running(status_div) {
var result = 0;
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 700; j++) {
for (var k = 0; k < 200; k++) {
result = result + i + j + k;
}
}
}
$(status_div).text('calclation done');
}
// Assign events to buttons
$('#do').on('click', function () {
$('#status').text('calculating....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('calculating....');
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});
</script>
</body></html>
Run Code Online (Sandbox Code Playgroud)
要进行测试,您需要将Internet Explorer 8的嵌套循环边界更改为300/100/100; 或者对于Chrome的1000/1000/500,由于"这个JS花费太长时间"的错误以及JavaScript引擎速度的不同灵敏度.
window.setTimeout()在 Ubuntu中有当前(2016 年 6 月 28 日)实现的副本。
正如我们所看到的,定时器被这行代码插入:
nsAutoPtr<TimeoutInfo>* insertedInfo =
mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
Run Code Online (Sandbox Code Playgroud)
然后下面几行你有一个if()声明:
if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
...
Run Code Online (Sandbox Code Playgroud)
该insertedInfo == mTimeouts.Elements()检查是否这是刚插入已经超时定时器。下面的块不执行附加的函数,但主循环会立即注意到计时器超时,因此它将跳过您期望的空闲状态(CPU 的产量)。
这清楚地(至少对我而言)解释了您正在经历的行为。屏幕上的渲染是另一个进程(任务/线程),需要为该其他进程放弃 CPU 以获得重新绘制屏幕的机会。为此,您需要等待足够长的时间,以便您的计时器函数不会立即执行并产生收益。
正如您所注意到的,暂停 500 毫秒即可解决问题。您可能可以使用较小的数字,例如 50 毫秒。无论哪种方式,它都不能保证会发生收益,但是如果运行该代码的计算机当前没有被淹没(即防病毒软件当前没有在后台全速运行...... )
SetTimeout()来自 Firefox的完整功能:
(在源文件的位置:dom/workers/WorkerPrivate.cpp)
int32_t
WorkerPrivate::SetTimeout(JSContext* aCx,
dom::Function* aHandler,
const nsAString& aStringHandler,
int32_t aTimeout,
const Sequence<JS::Value>& aArguments,
bool aIsInterval,
ErrorResult& aRv)
{
AssertIsOnWorkerThread();
const int32_t timerId = mNextTimeoutId++;
Status currentStatus;
{
MutexAutoLock lock(mMutex);
currentStatus = mStatus;
}
// It's a script bug if setTimeout/setInterval are called from a close handler
// so throw an exception.
if (currentStatus == Closing) {
JS_ReportError(aCx, "Cannot schedule timeouts from the close handler!");
}
// If the worker is trying to call setTimeout/setInterval and the parent
// thread has initiated the close process then just silently fail.
if (currentStatus >= Closing) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
newInfo->mIsInterval = aIsInterval;
newInfo->mId = timerId;
if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
NS_WARNING("Timeout ids overflowed!");
mNextTimeoutId = 1;
}
// Take care of the main argument.
if (aHandler) {
newInfo->mTimeoutCallable = JS::ObjectValue(*aHandler->Callable());
}
else if (!aStringHandler.IsEmpty()) {
newInfo->mTimeoutString = aStringHandler;
}
else {
JS_ReportError(aCx, "Useless %s call (missing quotes around argument?)",
aIsInterval ? "setInterval" : "setTimeout");
return 0;
}
// See if any of the optional arguments were passed.
aTimeout = std::max(0, aTimeout);
newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
uint32_t argc = aArguments.Length();
if (argc && !newInfo->mTimeoutCallable.isUndefined()) {
nsTArray<JS::Heap<JS::Value>> extraArgVals(argc);
for (uint32_t index = 0; index < argc; index++) {
extraArgVals.AppendElement(aArguments[index]);
}
newInfo->mExtraArgVals.SwapElements(extraArgVals);
}
newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval;
if (!newInfo->mTimeoutString.IsEmpty()) {
if (!nsJSUtils::GetCallingLocation(aCx, newInfo->mFilename, &newInfo->mLineNumber)) {
NS_WARNING("Failed to get calling location!");
}
}
nsAutoPtr<TimeoutInfo>* insertedInfo =
mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n",
this, aTimeout, aIsInterval ? "yes" : "no"));
// If the timeout we just made is set to fire next then we need to update the
// timer, unless we're currently running timeouts.
if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
nsresult rv;
if (!mTimer) {
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return 0;
}
mTimerRunnable = new TimerRunnable(this);
}
if (!mTimerRunning) {
if (!ModifyBusyCountFromWorker(true)) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
mTimerRunning = true;
}
if (!RescheduleTimeoutTimer(aCx)) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
}
return timerId;
}
Run Code Online (Sandbox Code Playgroud)
重要提示: JavaScript 指令yield与我所说的无关。我说的是sched_yield()功能,它在二进制进程调用某些函数时发生,例如它sched_yield()自己poll()、select()、 等。