ホームページ  >  記事  >  ウェブフロントエンド  >  ブラウザ UI マルチスレッドと JavaScript シングルスレッドの基礎となる動作メカニズムの理解

ブラウザ UI マルチスレッドと JavaScript シングルスレッドの基礎となる動作メカニズムの理解

黄舟
黄舟オリジナル
2017-02-28 14:37:571273ブラウズ

初めて JavaScript を学んだときから、私はこの考えを「植え付けられ」ました

JavaScript はシングルスレッドです
しかし、継続的な学習プロセスで
タイマーと ajax の非同期読み込みについて学びました
この文を疑ってください
JavaScript はシングルスレッドであるのに、なぜ依然として非同期読み込みが行われるのでしょうか?
後で、ブラウザには js スレッドだけではなく、他のスレッドと一緒に存在することを知りました - ブラウザ UI マルチスレッド

このような記事をずっと書きたいと思っていましたが、私は十分に理解していませんが、私の書いたことが皆さんを誤解させるのではないかと心配です
ネット上で皆さんが書いたさまざまなブログ記事を読んで、私はさらに混乱しました
でも、最終的には勇気を出して決意しました。それについて話しましょう╮(╯_╰)╭
さて、JavaScript のスレッドは脇に置いてください問題については話さないで、まずブラウザ UI スレッドとは何かを見てみましょう

ブラウザ UI マルチスレッド

前の写真を見てみましょうまず

ブラウザ UI マルチスレッドと JavaScript シングルスレッドの基礎となる動作メカニズムの理解

通常の状況では、ブラウザには少なくとも 3 つの常駐スレッドがあります

  • GUI レンダリング スレッド (ページのレンダリング)

  • JS エンジン スレッド (処理スクリプト)

  • イベントトリガースレッド (制御インタラクション)

ブラウザを含むと、新しいスレッドが開かれることがあります。たとえば、Http リクエスト スレッド (Ajax) と使用後に破棄されるその他のスレッド
これらは一緒になってブラウザの UI スレッドを構成します
これらのスレッドは機能しますUIスレッドの制御下で整然とした方法で実行されます
この常駐スレッドに関するオンラインの意見は一貫性がありません。各ブラウザの実装は異なる可能性があるため、ここでは詳しく説明しません

jsエンジンスレッドを右下隅、これはブラウザのメインスレッドです
そして、GUI レンダリング スレッドとは互換性がありません
理由は非常に簡単で、すべての JS スレッドを操作する必要があるからです。特定の DOM スタイルが必要な場合、レンダリング エンジンの動作を停止する必要があります (横暴な社長が、そこに立って動かないように要求します)

js シングルスレッド

JavaScript がシングルスレッドである理由
シングルスレッドとは、次のことしか実行できないことを意味します同時に 1 つのことを実行します
それでは、JavaScript がマルチスレッドであることは良くないのでしょうか? それはどれほど効率的ですか?
それは良くありません
js はユーザーと対話し、DOM を処理するように設計されています
JavaScript がマルチスレッドである場合、マルチスレッド同期の問題に対処する必要があります (C++ スレッド同期に支配される恐怖を漠然と覚えています)
js がマルチスレッドの場合、同時に 1 つのスレッドが DOM を変更したいと考え、別のスレッドが DOM を変更したいと考えます。 DOMを削除してください
問題はさらに複雑になります。「ロック」メカニズムが導入されると、それは非常に面倒になります(それではフロントエンドを学ばなくなります( ̄_ , ̄))
なので、私たちのようなスクリプト言語を開発する必要はありません。非常に複雑なので、JavaScriptは誕生以来シングルスレッドで実行されています

H5はWeb Workerを提案しましたが、DOMを操作することはできません。それを解決するには、まだ兄貴分の JS メインスレッドに任せる必要があります
この Web Worker についてはあまり詳しくないので、ここで説明します。あまり言うことはありません
これらのサブスレッドはメインスレッドによって完全に制御されているため、 JavaScript のシングルスレッドの性質は変わりません

実行スタック

まず実行スタックとは何かを見てみましょう
スタックは先入れ後出し (FILO) データです構造
実行スタックには格納されます実行されるタスク。各タスクは「フレーム」と呼ばれます
たとえば

function foo(c){
    var a = 1;
    bar(200);
}function bar(d){
    var b = 2;
}
foo(100);

実行スタックがどのように変化したかを見てみましょう

  • 最初、コードが実行されていないとき、実行スタックはfoo 関数が実行されると、フレームが作成され、このフレームには仮パラメータとローカル変数が含まれます (プリコンパイル プロセス)。その後、関数内のコードが実行されます。

  • 新しいフレームを作成します。これには仮パラメータとローカル変数も含まれ、スタックにプッシュされます

  • bar関数が実行された後、スタックがポップされます

  • foo関数が実行されます、そしてスタックはポップされます

  • 実行スタックは空です

  • 実行スタックは実際にはJSメインスレッドと同等です

  • タスクキュー
  • キューは先入れ先出し(FIFO)データです構造

    js スレッドにもタスク キューがあります
  • タスク キューには一連の保留中のタスクが含まれます
単一スレッドとは、すべてのタスクを次々に実行する必要があることを意味します。1 つのタスクの実行に時間がかかりすぎると、後続のタスクが実行されなくなります。待たなければなりません

それは、看護師が列に並んでいる子供に注射をするようなものです。前の子供が針を転がし続けると、後ろの子供は待たなければなりません(この比喩は不適切だと思われます)

しかし、もし子供が前の子が針で気を失いました

そのとき、看護師のおばさんはそこに座って彼を待つことができません。目が覚めたら、まず後ろの子供に鍼を打たなければなりません

これは、その子供を(非同期的に)「首を吊る」ことに等しいです


、タスクは 2 つのタイプに分けることができます



同期タスク

非同期タスク

同步任务就是正在主线程执行栈中执行的任务(在屋子内打针的小朋友)
而异步任务是在任务队列等候处理的任务(在屋子外等候打针的小朋友)
一旦执行栈中没有任务了,它就会从执行队列中获取任务执行

事件与回调

任务队列是一个事件的队列,IO设备(输入/输出设备)每完成一项任务,就会在任务队列中添加事件处理
用户触发了事件,也同样会将回调添加到任务队列中去
主线程执行异步任务,便是执行回调函数(事件处理函数)
只要执行栈一空,排在执行队列前面的会被优先读取执行,
不过主线程会检查时间,某些事件需要到了规定时间才能进入主线程处理(定时器事件)

事件循环

主线程从执行队列不断地获取任务,这个过程是循环不断地,叫做“Event Loop”事件循环
同步任务总是会在异步任务之前执行
只有当前的脚本执行完,才能够去拿任务队列中的任务执行
前面也说到了,任务队列中的事件可以是定时器事件
定时器分为两种 setTimeout() 和 setInterval()
前者是定时执行一次,后者定时重复执行
第一个参数为执行的回调函数,第二个参数是间隔时间(ms)
来看这样一个例子

setTimeout(function(){
    console.log('timer');
},1000);console.log(1);console.log(2);console.log(3);

这个没什么问题,浏览器打印的是 1 2 3 timer
但是这样呢

setTimeout(function(){
    console.log('timer');
},0);//0延时console.log(1);
console.log(2);
console.log(3);

浏览器打印依然打印的是 1 2 3 timer
也许有同学知道,旧版浏览器,setTimeout定时至少是10ms(即便你设置了0ms),
H5新规范是定时至少4ms(我读书少不知道为什么),改变DOM也是至少16ms
也许这是因为这个原因
那么我再改动一下代码

setTimeout(function(){
    console.log('timer');
},0);var a = +new Date();for(var i = 0; i < 1e5; i++){
    console.log(1);
}var b = +new Date();
console.log(b - a);

这回够刺激了吧,输出10w次,我浏览器都假死了(心疼我chrome)
不仅如此,我还打印了循环所用时间
来看看控制台

输出了10w个1,用了将近7s
timer依然是最后打印的
这就证明了我前面说的话: 同步任务总是会在异步任务之前执行
只有我执行栈空了,才会去你任务队列中取任务执行

实例

最后我举一个例子加深一下理解

demo.onclick = function(){
    console.log(&#39;click&#39;);
}function foo(a){
    var b = 1;
    bar(200);
}function bar(c){
    var d = 2;
    click//伪代码 此时触发了click事件(这里我假装程序运行到这里手动点击了demo)
    setTimeout(function(){
        console.log(&#39;timer&#39;);
    }, 0);
}
foo(100);

怕大家蒙我就不写Ajax了
Ajax如果处理结束后(通过Http请求线程),也会将回调函数放在任务队列中
还有一点click那一行伪代码我最开始是想用demo.click()模拟触发事件
后来在测试过程中,发现它好像跟真实触发事件不太一样
它应该是不通过触发事件线程,而是存在于执行栈中,就相当于单纯地执行click回调函数
不过这只是我自己的想法有待考证,不过这不是重点,重点是我们理解这个过程,请大家不要吐槽我╰( ̄▽ ̄)╭

下面看看执行这段代码时发生了什么(主要说栈和队列的问题,不会赘述预编译过程)

  • 主线程开始执行,产生了栈、堆、队列

  • demo节点绑定了事件click,交给事件触发线程异步监听

  • 执行foo函数(之前同样有预编译过程),创建了帧包括foo函数的形参、局部变量压入执行栈中

  • foo函数内执行bar函数,创建帧包括bar函数的形参、局部变量压入执行栈中

  • 触发了click事件,事件触发线程将回调事件处理函数放到js线程的任务队列中

  • 触发了定时器事件,事件触发线程立即(4ms)将回调处理函数放到js线程的任务队列中

  • bar函数执行完毕,弹出栈

  • foo函数执行完毕,弹出栈

  • 此时执行栈为空

  • 执行栈向任务队列中获取一个任务:click回调函数,输出‘click’

  • 执行栈项任务队列中获取一个任务:定时器回调函数,输出‘timer’

  • 执行结束

这里从任务队列里不断取任务的过程就是Event Loop

Event Loop

有一些我的理解,如果发现不对或者有疑问的地方,请联系我
相信大家看了这个例子应该对js底层运行机制有了一个大概的了解

 以上就是ブラウザ UI マルチスレッドと JavaScript シングルスレッドの基礎となる動作メカニズムの理解及对JavaScript单线程底层运行机制的理解的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。