6

Гарантирован ли однопоточный характер JavaScript?

4

JavaScript известен тем, что является однопоточным во всех современных реализациях браузеров. Однако, задействована ли эта характеристика в каких-либо стандартах, или это просто традиция? Можно ли с уверенностью утверждать, что JavaScript всегда будет однопоточным?

5 ответ(ов)

6

Это хороший вопрос. Я бы с радостью сказал "да", но не могу.

JavaScript обычно считается однопоточным для исполняемых сценариев (*), что означает, что когда ваш встроенный сценарий, обработчик событий или таймаут вызывается, вы полностью контролируете поток выполнения до тех пор, пока не закончите выполнение своего блока или функции.

(*: игнорируя вопрос о том, действительно ли браузеры реализуют свои JS-движки с использованием одного потока ОС или же вводятся другие ограниченные потоки выполнения, такие как WebWorkers.)

Тем не менее, на практике это не совсем так, и это может происходить по хитрым причинам.

Самый распространенный случай — это немедленные события. Браузеры вызывают их сразу, как только ваш код делает что-то, что вызывает их:

var l = document.getElementById('log');
var i = document.getElementById('inp');
i.onblur = function() {
    l.value += 'blur\n';
};
setTimeout(function() {
    l.value += 'log in\n';
    l.focus();
    l.value += 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

В результате мы получаем log in, blur, log out во всех браузерах, кроме IE. Эти события не просто происходят потому, что вы вызвали focus(), они могут произойти также из-за вызова alert(), открытия поп-апа или любого другого действия, изменяющего фокус.

Это также может привести к возникновению других событий. Например, добавив i.onchange слушатель и введя что-то в поле ввода до того, как focus() уберет фокус, порядок в логе будет log in, change, blur, log out, кроме Opera, где он будет log in, blur, log out, change, и IE, где (даже менее объяснимо) log in, change, log out, blur.

Аналогично, вызов click() на элементе, предоставляющем это событие, сразу же вызывает обработчик onclick во всех браузерах (по крайней мере, это согласованно!).

(Я здесь использую прямые свойства обработчиков событий on..., но то же самое происходит с addEventListener и attachEvent.)

Существуют также различные обстоятельства, при которых события могут возникать, даже если ваш код всё еще выполняется, несмотря на то, что вы ничего не сделали, чтобы вызвать это. Например:

var l = document.getElementById('log');
document.getElementById('act').onclick = function() {
    l.value += 'alert in\n';
    alert('alert!');
    l.value += 'alert out\n';
};
window.onresize = function() {
    l.value += 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

Нажмите кнопку alert, и вы увидите модальное диалоговое окно. Скрипт больше не выполняется, пока вы не закроете это диалоговое окно, не так ли? Нет. Измените размер основного окна, и вы получите alert in, resize, alert out в текстовом поле.

Вы можете подумать, что невозможно изменить размер окна, пока открыто модальное диалоговое окно, но это не так: в Linux вы можете изменять размер окна сколько угодно; в Windows это немного сложнее, но это возможно, если вы измените разрешение экрана с большего на меньшее, при котором окно не помещается, что приводит к его изменению размера.

Вы можете подумать, что только события resize (и, вероятно, еще некоторые, такие как scroll) могут возникать, когда у пользователя нет активного взаимодействия с браузером, потому что скрипт в потоке выполнения. И в случае с однооконными приложениями вы можете быть правы. Однако все это меняется, как только вы начинаете кросс-оконное взаимодействие. Во всех браузерах, кроме Safari, который блокирует все окна/вкладки/фреймы, когда одно из них занято, вы можете взаимодействовать с документом из кода другого документа, работающего в отдельном потоке исполнения и вызывающего любые связанные обработчики событий.

Места, где события могут возникать, пока скрипт ещё выполняется:

  • при открытых модальных всплывающих окнах (alert, confirm, prompt) во всех браузерах, кроме Opera;
  • во время showModalDialog в браузерах, которые его поддерживают;
  • диалоговое окно “Скрипт на этой странице может быть занят...”, даже если вы решите позволить скрипту продолжить выполнение, позволяет событиям, таким как resize и blur, возникать и обрабатываться, даже пока скрипт находится в середине занимающего цикла, кроме как в Opera.
  • довольно давно, в IE с плагином Sun Java, вызов любого метода на апплете мог позволить событиям возникать и скрипту повторно входить. Это всегда было ошибкой, связанной со временем, и, возможно, Sun исправили это с тех пор (я искренне надеюсь на это).
  • вероятно, больше. Прошло некоторое время с тех пор, как я это тестировал, и браузеры стали сложнее.

В заключение, JavaScript большинству пользователей, большинство времени, кажется строго одноименным и событийно-дривенным языком выполнения. На самом деле это не так. Неясно, сколько из этого является просто ошибкой, а сколько — преднамеренным дизайном, но если вы создаете сложные приложения, особенно те, которые взаимодействуют через окна/фреймы, есть все шансы, что это может вызвать у вас проблемы — и это произойдет в непостоянных, трудноотлаживаемых формах.

Если возникнут серьезные проблемы, вы можете решить проблемы с конкурентностью, откладывая все ответы на события. Когда событие возникает, помещайте его в очередь и обрабатывайте очередь позже, в функции setInterval. Если вы пишете фреймворк, который должен использоваться в сложных приложениях, это может быть хорошим шагом. postMessage также, надеюсь, упростит взаимодействие между документами в будущем.

0

Да, хотя при использовании любых асинхронных API, таких как setInterval и обратные вызовы xmlhttp, вы все равно можете столкнуться с проблемами, связанными с конкурентным программированием, в основном с гонками состояний.

0

Да, хотя Internet Explorer 9 компилирует ваш JavaScript в отдельном потоке в подготовке к выполнению на основном потоке. Для вас как разработчика это ничего не меняет.

0

JavaScript/ECMAScript разработан для работы в рамках хост-окружения. Это значит, что JavaScript на самом деле не делает ничего, пока хост-окружение не решит разобрать и выполнить данный скрипт, а также не предоставит объекты окружения, которые делают JavaScript действительно полезным (например, DOM в браузерах).

Я полагаю, что конкретная функция или блок скрипта будут выполняться построчно, и это гарантировано для JavaScript. Тем не менее, хост-окружение может выполнять несколько скриптов одновременно. Кроме того, хост-окружение всегда может предоставить объект, который позволит использовать многопоточность. setTimeout и setInterval являются примерами, или, по крайней мере, псевдо-примерами, как хост-окружение может обеспечить некоторую конкуренцию (даже если это не совсем конкуренция).

0

На самом деле родительское окно может взаимодействовать с дочерними или соседними окнами и фреймами, у которых есть свои собственные потоки выполнения.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь