Гарантирован ли однопоточный характер JavaScript?
JavaScript известен тем, что является однопоточным во всех современных реализациях браузеров. Однако, задействована ли эта характеристика в каких-либо стандартах, или это просто традиция? Можно ли с уверенностью утверждать, что JavaScript всегда будет однопоточным?
5 ответ(ов)
Это хороший вопрос. Я бы с радостью сказал "да", но не могу.
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
также, надеюсь, упростит взаимодействие между документами в будущем.
Да, хотя при использовании любых асинхронных API, таких как setInterval и обратные вызовы xmlhttp, вы все равно можете столкнуться с проблемами, связанными с конкурентным программированием, в основном с гонками состояний.
Да, хотя Internet Explorer 9 компилирует ваш JavaScript в отдельном потоке в подготовке к выполнению на основном потоке. Для вас как разработчика это ничего не меняет.
JavaScript/ECMAScript разработан для работы в рамках хост-окружения. Это значит, что JavaScript на самом деле не делает ничего, пока хост-окружение не решит разобрать и выполнить данный скрипт, а также не предоставит объекты окружения, которые делают JavaScript действительно полезным (например, DOM в браузерах).
Я полагаю, что конкретная функция или блок скрипта будут выполняться построчно, и это гарантировано для JavaScript. Тем не менее, хост-окружение может выполнять несколько скриптов одновременно. Кроме того, хост-окружение всегда может предоставить объект, который позволит использовать многопоточность. setTimeout
и setInterval
являются примерами, или, по крайней мере, псевдо-примерами, как хост-окружение может обеспечить некоторую конкуренцию (даже если это не совсем конкуренция).
На самом деле родительское окно может взаимодействовать с дочерними или соседними окнами и фреймами, у которых есть свои собственные потоки выполнения.
Где найти документацию по форматированию даты в JavaScript?
В чем разница между String.slice и String.substring?
Проверка соответствия строки регулярному выражению в JS
Как создать диалог с кнопками "Ок" и "Отмена"
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?