JavaScript Asynchrony:理解したい人のためのハンドブック

画像







JavaScriptは簡単に記述できます。 数個のライブラリまたはファッショナブルなフレームワークを用意し、簡単なチュートリアルを読むだけで十分です。数時間で、シンプルな作業インターフェースが完成します。







インターフェイスがより複雑になると、問題が始まります。 これは、JavaScriptを深く理解することが不可欠な場所です。 大きくて複雑なインターフェースでも、高速で応答性が高いことが重要です。 通常、応答性は非同期関数を使用して実現されます。 JavaScriptの非同期がどのように機能するかを理解してみましょう。







JavaScriptにはマルチスレッドがありません。 既にwebworkersを完全に使用できるという事実にもかかわらず、それらからDOMを変更したり、windowオブジェクトのメソッドを呼び出したりすることはできません。 一言で言えば、マルチスレッドではなく、まったくがっかりです。







そのような制限の理由は明らかです。 2つの並列スレッドが、DOM内の同じノードを変更しようと競い合い、予測できない結果になると想像してください。 提示? 私も不安を感じました。







画像







データの整合性と一貫性を保証するために1つのスレッドでDOMツリーと連携しますが、1つのスレッドでインターフェイスをプログラムする方法は? 結局のところ、インターフェイスの本質は非同期です。 この目的のために、非同期関数が考えられます。 これらはすぐにではなく、イベント後に実行されます。 興味深いことに、これらの関数はJavaScriptエンジンの一部ではありません。 V8にはそのような関数がないため、純粋なV8でsetTimeoutを呼び出すとエラーが発生します。 次に、setTimeout、requestAnimationFrame、またはaddEventListenerはどこから来たのですか?







内部の非同期性



JavaScriptエンジンは肉挽き器のようなもので、コールスタックから順番に取得される操作を際限なく挽きます(1)。 コードは直線的かつ連続的に実行されます。 スタックから操作を削除することはできず、実行スレッドにのみ割り込みできます。 アラートや「例外」などを呼び出すと、実行のスレッドが中断されます。







画像







各操作には、コンテキスト(データが利用可能なメモリの特定の領域)が含まれます。 コンテキストは、ツリーの形でメモリ内に配置されます。 ツリーの各リーフには、親ブランチとルート(グローバルスコープ)で定義されたスコープがあります。 JavaScriptの関数はデータであり、データとまったく同じようにメモリに保存されるため、変数として転送されるか、他の関数から返されます。







非同期操作はエンジンではなく環境で実行されます(5.6)。 忘れられていたように、これは完全に真実ではありません:呼び出しスタックから関数を呼び出しキューにすぐに入れることができ、したがってクリーンエンジンも非同期に動作します)

環境はエンジンの上部構造です。 V8エンジン用のNodeJSとChrome、Gecko用のFirefox。 環境はWeb APIと呼ばれることもあります。

非同期呼び出しを作成するには、後で実行されるか、まったく実行されない関数へのリンクがWeb APIに渡されます。







関数には、独自のコンテキストまたはメモリ領域(3)が定義されています。 関数は、このメモリ領域とこのメモリ領域のすべての親にアクセスできます。 このような関数はクロージャーと呼ばれます。 この観点から、JavaScriptの関数はすべてコンテキストを持っているため、クロージャーです。







Web APIとJavaScrtiptエンジンは独立して動作します。 Web APIは、関数がコールキューに移動するポイントを決定します(2)。







呼び出しキュー内の関数はJavaScriptエンジンに分類され、そこで1つずつ実行されます。 実行は、関数がキューに入れられるのと同じ順序で発生します。







環境は、渡されたコードをいつコールキューに追加するかを独自に決定します。 キューの関数は、呼び出しスタックが現在の関数での作業を終了する前に実行スタックに追加されます(実行されます)。

したがって、コールスタックは同期的に動作し、Web APIは非同期的に動作します。







これは非常に重要です! 開発者はリソースへの並列アクセスを制御する必要はありません。環境は彼のために非同期作業を実行します。 node.jsではネットワークアプリケーションを記述するか、ハードドライブに直接アクセスし、Chromeでは同じエンジンを使用してボタンクリックをインターセプトするため、環境によってブラウザーとnode.jsの違いが決まります。







コールキュー内の個々の操作をキャンセルすることはできません。 これは環境で実行されます(例として、removeEventListener)。









呼び出しスタックをロードして、無期限に実行され、呼び出しキュー内の次の関数が呼び出されないようにすることができます。 たとえば、このコードを実行してみてください。







document.addEventListener('click', function(){ console.log('clicked') }); while(true){ console.log('wait'); }
      
      





クリックハンドラーは機能せず、無限ループによりコンピュータープロセッサが読み込まれます。 タブがフリーズします;)







別の例を示します。









クリックすると、「重い」関数が計算されます。 コンソールをクリックすると、関数の実行の最後にstartが書き込まれます-end。 私のラップトップで機能を完了するのに数秒かかります。 機能の実行中は、ボックスが点滅します。 つまり、CSSのアニメーションはJavaScriptコードとは非同期に実行されます。







しかし、不透明度の代わりにサイズを変更するとどうなりますか?









ボックスは、関数の実行中はフリーズします。 問題は、CSSのheightプロパティがDOMにアクセスすることです。 覚えているとおり、DOMは1つのスレッドからのみアクセスできるため、同時アクセスに問題はありません。







アニメーションの場合、DOMを変更しないプロパティ(変換、不透明度など)を使用する方が適切であると結論付けています。 JavaScriptのすべてのハードワークは、非同期で行うのが最適です。 たとえば、 このように。









コードはわかりやすくするために書かれており、ひざの上では、戦闘での使用は推奨されていません 。 大量の作業を小さな作業に分割し、非同期的に実行します。 ただし、インターフェイスはブロックされません。 このような計算には、Web Workerを使用できます。







おわりに



JavaScriptのおかげで、マルチスレッドを考慮することなく、データの整合性と一貫性について非同期アプリケーションを作成できます。 これらの利点を得るために、膨大な数のコールバックを支払い、メインスレッドをブロックし、常にコンテキストを失います。







次回は、最後の問題に対処する方法についてお話します。








All Articles