2015-05-19 4 views
2

Поскольку JavaScript является последовательным (не считая асинхронную способность), то почему это не «казаться» вести себя последовательным, как в этом упрощенном примере: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'); 
}; 

Наконец, что будет вызывать инвалидов атрибут не вступит в силу до тех пор, после для-цикла выполнения имеет час appened? Он визуально проверяется, просто комментируя строку атрибута remove.

Следует отметить, что нет необходимости в отложенном обратном вызове, обещании или чем-либо асинхронном; однако единственная работа, которую я обнаружил, заключалась в том, чтобы окружить петлю цикла и оставшиеся строки в обратном вызове setTimeout с нулевой задержкой, который помещает его в новый стек ... но действительно ?, setTimeout для чего-то, что должно работать по существу по очереди ?

Что здесь происходит и почему не происходит setAttribute до запуска цикла for?

+1

Браузер не делают изменения в DOM до возврата. – Barmar

+3

Это позволяет функции делать много изменений, и вы не видите, что все они меняются постепенно. Когда он возвращается, обновленный DOM отображается за один шаг. – Barmar

+1

@Barmar Это имеет прекрасный смысл. Спасибо. У вас есть ссылка ECMAScript или другое место для официальной ссылки и более подробная информация об этом процессе? –

ответ

0

браузер не не делает изменения в DOM, пока функции возвращается. - @Barmar

Per @ комментарии Barmar в и много дополнительного чтения по этому вопросу, я буду включать резюме со ссылкой на моем примере:

  • JavaScript однопоточный, так что только один процесс в может произойти время
  • Rendering (repaint & reflow) - это отдельный/визуальный процесс, который выполняет браузер, поэтому он приходит после. Эта функция заканчивается, чтобы избежать потенциально тяжелых вычислений CPU/GPU, которые могут вызвать проблемы с производительностью/визуализацией, если они отображаются на лету

Обобщенный другой способ это цитата из http://javascript.info/tutorial/events-and-timing-depth#javascript-execution-and-rendering

В большинстве браузеров, рендеринга и JavaScript используют одну очередь событий. Это означает, что во время работы JavaScript рендеринг не выполняется.

Чтобы объяснить это по-другому, я буду использовать SetTimeout «взломать» я упомянул в моем вопросе:

  1. Нажатие на кнопку «Выполнить» ставит свою функцию в стеке/очереди вещи для браузера, чтобы выполнить
  2. Увидев атрибут «disabled», браузер затем добавляет процесс рендеринга в стек/очередь задач.
  3. Если мы добавим setTimeout к тяжелой части функции, setTimeout (по дизайну) вытащит ее из текущего потока и добавит ее в конец стека/очереди. Это означает, что начальные строки кода будут работать, , затем рендеринг отключенного атрибута, , затем долгосрочный код для цикла; все в порядке стека, поскольку оно было поставлено в очередь.

Дополнительные ресурсы и пояснения относительно выше:

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, что позволяет браузеру «дышать» и делать перерисовку, потому что думает, что ваш текущий поток выполнения 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); 

}; 
+0

Почему downvote? – jfriend00

+1

Идея. Я сбалансировал это для вас. Другой возможный способ сделать повторный макет, который, конечно, немного тяжелее, состоит в том, чтобы поместить остальную часть вашего кода внутри 'setTimeout()', без второго аргумента. Я сделал это в тех случаях, когда, например, вы хотите, чтобы что-то применялось к стилям, и * затем * переходило в другое состояние стиля (с использованием переходов CSS3) – Katana314

+0

@ Katana314 - согласен с опцией 'setTimeout()' что в моем ответе теперь тоже (возможно, добавил его, пока вы читали). Я использовал его для переходов CSS3. – jfriend00