ホームページ >ウェブフロントエンド >jsチュートリアル >js の非同期原理の問題についての深い理解

js の非同期原理の問題についての深い理解

零到壹度
零到壹度オリジナル
2018-04-09 11:48:051666ブラウズ

JS はシングルスレッドであるとよく言います。たとえば、Node.js セミナーでは、JS の機能の 1 つはシングルスレッドであるため、JS はよりシンプルで明確になると誰もが言いました。しかし、皆さんは本当に理解していますか? JSのシングルスレッド機構というのでしょうか?シングルスレッドの場合、イベントベースの非同期機構はどうあるべきか? この知識は「JavaScript 決定版ガイド」には紹介されておらず、私は海外の記事を読んで初めて理解できました。ここで皆さんとシェアさせていただきます。翻訳作業中に、この記事をすでに翻訳されている方がいることを発見したので、そこからいくつかの文をお借りしました。記事のURL: リンク。後で「JavaScript Advanced Programming」で高度なタイマーとループタイマーが紹介されていることがわかりましたが、私が翻訳した原文よりも詳しく紹介されていないように感じました。私の文章が下手だと思われる場合は、元の外国語を確認してください。言語

1 を最初に読んでください。 次の 2 つの例 1 1.1. 単純な Settimeout

setTimeout(function () { while (true) { } }, 1000);
setTimeout(function () { alert('end 2'); }, 2000);
setTimeout(function () { alert('end 1'); }, 100);
alert('end');

は、「End '、' End 1 」の結果を実行し、ブラウザは偽装しますが、「End 2」はポップしません。上。つまり、最初の settimeout は無限ループで実行され、理論的には 1 秒後に実行される 2 番目の settimeout の関数が直接ブロックされます。これは、私たちが通常理解している非同期関数のマルチスレッドとは異なります。お互いに干渉しないようにするのは矛盾しています。

添付されたタイマーの使用法

-

-初始化一个简单的js的计时器,一段时间后,才触发并执行回调函数。 setTimeout 返回一个唯一id,可用这个id来取消这个计时器。
var id = setTimeout(fn,delay);
 
--类似于setTimeout,不一样的是,每隔一段时间,会持续调用回调fn,直到被取消
var id = setInterval(fn,delay);
 
--传入一个计时器的id,取消计时器。
clearInterval(id);
clearTimeout(id);

1.2. Ajaxリクエストコールバック

次に、メインコードは次のとおりです:

var xmlReq = createXMLHTTP();//创建一个xmlhttprequest对象
function testAsynRequest() {
    var url = "/AsyncHandler.ashx?action=ajax";
    xmlReq.open("post", url, true);
    xmlReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xmlReq.onreadystatechange = function () {
        if (xmlReq.readyState == 4) {
            if (xmlReq.status == 200) {
                var jsonData = eval('(' + xmlReq.responseText + ')');
                alert(jsonData.message);
            }
            else if (xmlReq.status == 404) {
                alert("Requested URL is not found.");
            } else if (xmlReq.status == 403) {
                alert("Access denied.");
            } else {
                alert("status is " + xmlReq.status);
            }
        }
    };
    xmlReq.send(null);
}
testAsynRequest();//1秒后调用回调函数
 
while (true) {
}

サーバー側で簡単な出力を実現します。
private void ProcessAjaxRequest(HttpContext context)
{
    string action = context.Request["ajax"];
    Thread.Sleep(1000);//等1秒
    string jsonObject = "{\"message\":\"" + action + "\"}";
    context.Response.Write(jsonObject);
}

理論的には、ajax が非同期リクエストを作成し、その非同期コールバック関数が別のスレッドにある場合、コールバック関数は他のスレッドによって「ブロック」されてはならず、スムーズに実行される必要があります。つまり、1 秒後にコールバックの実行はポップされます。 up 'ajax ' ですが、これは実際の状況ではなく、ブラウザが再び無限ループによりアニメーションを一時停止したため、コールバック関数を実行できません。

上記 2 つの例に基づいて、要約は次のようになります:

①    JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序.
②    JavaScript引擎用单线程运行也是有意义的,单线程不必理会线程同步这些复杂的问题,问题得到简化。

2. JavaScript エンジン

ただし、それを JS で内部的に実装する方法については、次に説明します。 タイマーの内部動作を理解する前に、トリガーと実行は同じ概念ではないことを明確にする必要があります。タイマーのコールバック関数は、指定された遅延時間の後に確実にトリガーされますが、必ずしもすぐに実行されるわけではありません。待つ必要があるかもしれません。すべての JavaScript コードはスレッド内で実行され、マウス クリックやタイマーなどのイベントは、JS 単一スレッドがアイドル状態のときにのみ実行されます。

JSのスレッド、イベントループ、タスクキューの紹介

JSはシングルスレッドですが、非同期タスクを実行できるのは主にイベントループ(Event Loop)とタスクキュー(Task Queue)があるためです。 ) JS で。

js の非同期原理の問題についての深い理解イベント ループ: JS は while (true) と同様のループを作成し、ループ本体が実行されるたびに、プロセスは Tick と呼ばれます。各 Tick のプロセスでは、保留中のイベントがあるかどうかを確認し、存在する場合は、関連するイベントとコールバック関数が取り出され、メインスレッドによる実行のために実行スタックに置かれます。保留中のイベントはタスク キューに保存されます。つまり、各 Tick はタスク キューに実行する必要があるタスクがあるかどうかを確認します。

タスクキュー: 非同期操作では、関連するコールバックがタスクキューに追加されます。さまざまな非同期操作がさまざまなタイミングでタスク キューに追加されます。たとえば、onclick、setTimeout、および ajax は、ブラウザ カーネルの Webcore によって実行されます。それらは、DOM バインディング、ネットワーク、およびタイマー モジュールです。

onclick はブラウザ カーネルの DOM Binding モジュールによって処理され、イベントがトリガーされると、コールバック関数がすぐにタスク キューに追加されます。

setTimeoutはブラウザカーネルのタイマーモジュールによって遅延され、時刻になるとコールバック関数がタスクキューに追加されます。

Ajaxはブラウザカーネルのネットワークモジュールによって処理され、ネットワークリクエストが完了して戻った後、コールバックがタスクキューに追加されます。

主线程:JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。

Update:

《你不知道的 JavaScript》一书中,重新讲解了 ES6 新增的任务队列,和上面的任务队列略有不同,上面的任务队列书中称为事件队列。

上面提到的任务(事件)队列是在事件循环中的,事件循环每一次 tick 便执行上面所述的任务(事件)队列中的一个任务。而任务(事件)队列是只能往尾部添加任务。

而 ES6 中新增的任务队列是在事件循环之上的,事件循环每次 tick 后会查看 ES6 的任务队列中是否有任务要执行,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高。

如 Promise 就使用了 ES6 的任务队列特性。 

3.       JavaScript引擎线程和其它侦听线程

   在浏览器中,JavaScript引擎是基于事件驱动的,这里的事件可看作是浏览器派给它的各种任务,这些任务可能源自当前执行的代码块,如调用setTimeout(),也可能来自浏览器内核,如onload()、onclick()、onmouseover()、setTimeOut()、setInterval()、Ajax等。如果从代码的角度来看,所谓的任务实体就是各种回调函数,由于“单线程”的原因,这些任务会进行排队,一个接着一个等待着被引擎处理。

 js の非同期原理の問題についての深い理解

上图中,定时器和事件都按时触发了,这表明JavaScript引擎的线程和计时器触发线程、事件触发线程是三个单独的线程,即使JavaScript引擎的线程被阻塞,其它两个触发线程都在运行。

  浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步。假如某一浏览器内核的实现至少有三个常驻线程: JavaScript引擎线程,事件触发线程,Http请求线程,下面通过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的。虽然每个浏览器内核实现细节不同,但这其中的调用原理都是大同小异。

     线程间通信:JavaScript引擎执行当前的代码块,其它诸如setTimeout给JS引擎添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发器时间到达通知,异步请求状态变更通知等.从代码角度看来任务实体就是各种回调函数,JavaScript引擎一直等待着任务队列中任务的到来.由于单线程关系,这些任务得进行排队,一个接着一个被引擎处理.

GUI渲染也是在引擎线程中执行的,脚本中执行对界面进行更新操作,如添加结点,删除结点或改变结点的外观等更新并不会立即体现出来,这些操作将保存在一个队列中,待JavaScript引擎空闲时才有机会渲染出来。来看例子(这块内容还有待验证,个人觉得当Dom渲染时,才可阻止渲染)

<p id="test">test</p>
<script type="text/javascript" language="javascript">
var i=0;
while(1) {
    document.getElementById("test").innerHTML+=i++ + "<br />";
}
</script>

  这段代码的本意是从0开始顺序显示数字,它们将一个接一个出现,现在我们来仔细研究一下代码,while(1)创建了一个无休止的循环,但是对于单线程的JavaScript引擎而言,在实际情况中就会造成浏览器暂停响应并处于假死状态。

  alert()会停止JS引擎的执行,直到按确认键,在JS调试的时候,查看当前实时页面的内容。

4.       setTimeout和 setInterval

回到文章开头,我们来看下setTimeout和setsetInterval的区别。

setTimeout(function(){
    /* Some long block of code ... */
    setTimout(arguments.callee,10);
},10);
 
setInterval(function(){
    /* Some long block of code ... */
},10);

  这两个程序段第一眼看上去是一样的,但并不是这样。setTimeout代码至少每隔10ms以上才执行一次;然而setInterval固定每隔10ms将尝试执行,不管它的回调函数的执行状态。

我们来总结下:

l JavaScript引擎只有一个线程,强制异步事件排队等待执行。
l setTimeout和setInterval在异步执行时,有着根本性不同。
l 如果一个计时器被阻塞执行,它将会延迟,直到下一个可执行点(这可能比期望的时间更长)
l setInterval的回调可能被不停的执行,中间没间隔(如果回调执行的时间超过预定等待的值)

《JavaScript高级程序设计》中,针对setInterval说法如下:

当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。还要注意两问题:

①    某些间隔会被跳过(抛弃);
② 多个定时器的代码执行之间的间隔可能会比预期小。此时可采取 setTimeout和setsetInterval的区别 的例子方法。

5.       Ajax异步

多くのクラスメートや友人が混乱しています。JavaScript は単一スレッドで実行されると言われているため、接続後の XMLHttpRequest は本当に非同期ですか?実際、リクエストは確かに非同期ですが、このリクエストはブラウザによって新しいスレッドを開くようにリクエストされます (上の図を参照)。コールバックが以前に設定されていた場合、リクエストのステータスが変化すると、非同期スレッドが生成されます。タスクが処理されると、JavaScript エンジンは常に単一スレッドでコールバック関数を実行します。つまり、onreadystatechange で設定された関数が引き続き実行されます。単一のスレッド。

ヒント: JavaScript エンジンの動作を理解することは非常に重要であり、特に多数の非同期イベントが (継続的に) 発生する場合には、プログラム コードの効率を向上させることができます。

以上がjs の非同期原理の問題についての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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