javascript - 带有 for 循环的 JavaScript 的顺序性质

标签 javascript for-loop sequential

既然 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');
};

最后,什么会导致 disabled 属性在 for 循环执行之后才生效?通过简单地注释掉 remove 属性行,可以直观地验证它。

我应该注意,不需要延迟回调、 promise 或任何异步;然而,我发现的唯一解决方法是将 for 循环和剩余行包围在一个零延迟的 setTimeout 回调中,这将它放在一个新的堆栈中......但真的吗?,setTimeout 用于基本上应该逐行工作的东西?

这里到底发生了什么,为什么 setAttribute 没有在 for 循环运行之前发生?

最佳答案

出于效率原因,浏览器不会立即布局并显示您对 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 回调中继续您的其余代码 - 从而让浏览器有机会“喘口气”并进行重绘,因为它认为您当前的 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);

};

关于javascript - 带有 for 循环的 JavaScript 的顺序性质,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30334834/

相关文章:

javascript - 拖放不起作用 : 'dropEffect' of undefined

javascript - 最后在 CRM 表单中加载 JavaScript 事件

javascript - 数组和切片的奇怪行为

javascript - 使用 Javascript For 循环更改像素颜色

c - 为什么 return 在此代码中不起作用?

javascript - 使用 jQuery 更改属性在 Chrome 中似乎不起作用

java - 让程序在自身内循环

linux - linux命令行的顺序FIFO队列

verilog - 如何实现超过一个时钟周期的时序逻辑?

go - 按顺序同时执行作业