JavaScript は、すべての最新のブラウザ実装でシングルスレッドであることが知られていますが、これは何らかの標準で指定されているのでしょうか、それとも単に伝統によるものなのでしょうか? JavaScript は常にシングルスレッドであると想定してもまったく問題ありませんか?
ベストアンサー1
それはいい質問ですね。「はい」と言いたいところですが、そうは言えません。
JavaScript は通常、スクリプト(*) から見える実行スレッドが 1 つあると考えられているため、インライン スクリプト、イベント リスナー、またはタイムアウトに入ると、ブロックまたは関数の最後から戻るまで完全に制御できます。
(*: ブラウザが実際に 1 つの OS スレッドを使用して JS エンジンを実装しているのか、それとも WebWorker によって他の制限された実行スレッドが導入されているのかという疑問は無視します。)
しかし、現実にはこれはまったく真実ではなく、意地悪なやり方です。
最も一般的なケースは即時イベントです。コードが何らかの原因でイベントを発生させると、ブラウザはすぐにイベントを起動します。
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 以外のすべてのブラウザーでは、いずれかのウィンドウ/タブ/フレームがビジー状態のときにそれらをすべてブロックしますが、別のドキュメントのコードからドキュメントを操作して、別の実行スレッドで実行し、関連するイベント ハンドラーを起動することができます。
スクリプトがまだスレッド化されている間に、生成できるイベントを発生させることができる場所:
Opera 以外のすべてのブラウザでモーダルポップアップ (
alert
、confirm
、 ) が開いているとき。prompt
サポートされているブラウザで
showModalDialog
は、「このページのスクリプトはビジー状態である可能性があります...」ダイアログボックスでは、スクリプトの実行を継続することを選択した場合でも、スクリプトがビジーループの途中であっても、サイズ変更やぼかしなどのイベントが発生し、処理される可能性があります (Opera を除く)。
少し前、Sun Java プラグインを使用した IE では、アプレットの任意のメソッドを呼び出すと、イベントが発生し、スクリプトが再入力される可能性がありました。これは常にタイミングに敏感なバグであり、その後 Sun が修正した可能性があります (そうであることを心から願っています)。
おそらくそれ以上です。これをテストしてからしばらく経ちましたが、それ以来ブラウザは複雑になっています。
要約すると、ほとんどのユーザーにとって、ほとんどの場合、JavaScript は厳密にイベント駆動型の単一実行スレッドを持っているように見えます。実際には、そのようなものはありません。これがどれだけ単なるバグで、どれだけ意図的な設計であるかは明らかではありませんが、複雑なアプリケーション、特にクロスウィンドウ/フレーム スクリプトのアプリケーションを作成している場合、これが問題になる可能性は十分にあります。しかも、断続的でデバッグが難しい方法で。
最悪の事態になった場合、すべてのイベント応答を間接化することで同時実行の問題を解決できます。イベントが到着したら、それをキューに入れて、後で関数内で順番にキューを処理しますsetInterval
。複雑なアプリケーションで使用することを意図したフレームワークを作成している場合は、これを行うと良いでしょう。postMessage
また、将来的には、クロスドキュメント スクリプトの苦痛が軽減されることが期待されます。