由于JavaScript是顺序的(不计算异步能力),那么为什么它不会"似乎"按照这个简化的例子来表现顺序:
HTML:
<input type="button" value="Run" onclick="run()"/>
JS:
var btn = document.querySelector('input');
var run = function() {
console.clear();
console.log('Running...');
var then = Date.now();
btn.setAttribute('disabled', 'disabled');
// Button doesn't actually get disabled here!!????
var result = 0.0;
for (var i = 0; i < 1000000; i++) {
result = i * Math.random();
}
/*
* This intentionally long-running worthless for-loop
* runs for 600ms on my computer (just to exaggerate this issue),
* meanwhile the button is still not disabled
* (it actually has the active state on it still
* from when I originally clicked it,
* technically allowing the user to add other instances
* of this function call to the single-threaded JavaScript stack).
*/
btn.removeAttribute('disabled');
/*
* The button is enabled now,
* but it wasn't disabled for 600ms (99.99%+) of the time!
*/
console.log((Date.now() - then) + ' Milliseconds');
};
最后,在 for循环执行发生之后,会导致disabled属性生效的原因是什么?只需注释掉删除属性行即可在视觉上验证。
我应该注意,不需要延迟回调,承诺或任何异步;然而,我发现的唯一工作是在零延迟的setTimeout回调中包围for循环和剩余行,这将把它放在一个新的堆栈中...但是真的吗?setTimeout应该基本上逐行工作?
这里真正发生了什么,为什么在for循环运行之前setAttribute没有发生?
答案 0 :(得分:1)
出于效率原因,浏览器不会立即布局并显示您在更改时立即对DOM所做的每一次更改。在许多情况下,DOM更新被收集到批处理中,然后在稍后的某个时间(例如,当前JS的完成时)完成更新。
这样做是因为如果一个Javascript对DOM进行了多次更改,那么重新布局文档然后重新发生每个更改的效率非常低效,并且要等到Javascript完成执行然后重新绘制时效率要高得多所有的变化一下子。
这是一个特定于浏览器的优化方案,因此每个浏览器都会自行决定何时重新绘制给定的更改,并且有一些事件可能导致/强制重绘。据我所知,这不是ECMAScript指定的行为,只是每个浏览器实现的性能优化。
在属性准确之前,有些DOM属性需要完成布局。通过Javascript访问这些属性(甚至只是阅读它们)将强制浏览器对任何挂起的DOM更改进行布局,并且通常也会导致重新绘制。其中一个属性是.offsetHeight
,还有其他属性(尽管此类别中的所有属性都具有相同的效果)。
例如,您可以通过更改此内容来重新绘制:
btn.setAttribute('disabled', 'disabled');
到此:
btn.setAttribute('disabled', 'disabled');
// read the offsetHeight to force a relayout and hopefully a repaint
var x = btn.offsetHeight;
如果你想进一步阅读这篇文章,那么Google search for "force browser repaint"就会有很多关于这个主题的文章。
如果浏览器仍然无法重新绘制,则其他解决方法是隐藏,然后显示一些元素(这会导致布局变脏)或使用setTimeout(fn, 1);
继续浏览你的代码在setTimeout回调中的其余部分 - 从而允许浏览器有机会“呼吸”#34;并重新绘制,因为它认为你当前的Javascript执行线程已经完成。
例如,您可以像这样实施setTimeout
解决方法:
var btn = document.querySelector('input');
var run = function() {
console.clear();
console.log('Running...');
var then = Date.now();
btn.setAttribute('disabled', 'disabled');
// allow a repaint here before the long-running task
setTimeout(function() {
var result = 0.0;
for (var i = 0; i < 1000000; i++) {
result = i * Math.random();
}
/*
* This intentionally long-running worthless for-loop
* runs for 600ms on my computer (just to exaggerate this issue),
* meanwhile the button is still not disabled
* (it actually has the active state on it still
* from when I originally clicked it,
* technically allowing the user to add other instances
* of this function call to the single-threaded JavaScript stack).
*/
btn.removeAttribute('disabled');
/*
* The button is enabled now,
* but it wasn't disabled for 600ms (99.99%+) of the time!
*/
console.log((Date.now() - then) + ' Milliseconds');
}, 0);
};
答案 1 :(得分:0)
在该函数之前,浏览器不会对DOM进行更改 回报。 - @Barmar
Per @ Barmar的评论和关于这个主题的大量补充阅读,我将包括一个参考我的例子的摘要:
另一种方式是来自http://javascript.info/tutorial/events-and-timing-depth#javascript-execution-and-rendering
的引用在大多数浏览器中,渲染和JavaScript使用单个事件队列。这意味着在JavaScript运行时,不会进行渲染。
为了解释另一种方式,我将使用我在问题中提到的 setTimeout “hack”:
有关上述内容的其他资源和解释: