Heim  >  Artikel  >  Web-Frontend  >  Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

不言
不言Original
2018-09-08 17:46:141881Durchsuche

Der Inhalt dieses Artikels befasst sich mit dem Verständnis der Javascript-Ereignisschleife. (Bilder und Text), es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen.

Stapel und Warteschlangen

Zugriff auf Daten im Computerspeicher. Die grundlegenden Datenstrukturen sind in Stapel und Warteschlangen unterteilt.

Stapel ist eine Last-In-First-Out-Datenstruktur, aber der „Heap“ ist eine andere komplexe Datenstruktur. Es sind zwei verschiedene Dinge. Das Merkmal des Stapels besteht darin, dass Operationen nur an einem Ende ausgeführt werden. Im Allgemeinen gibt es nur zwei Stapeloperationen: Push und Pop. Die ersten Daten, die auf den Stapel gelegt werden, sind immer die letzten, die ausgegeben werden.

Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

Die Warteschlange ähnelt dem Stapel, es handelt sich jedoch um eine First-In-First-Out-Datenstruktur. Der Vorgang des Einfügens von Daten wird von einem aus ausgeführt Ende der Warteschlange und Der Löschvorgang befindet sich auf der anderen Seite.

Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

Eine gängige Metapher ist, dass ein Stapel wie ein aufgerichteter Eimer ist. Die zuerst in den Stapel gelegten Daten werden am Boden des Eimers platziert Die Daten werden einzeln an der Öffnung des Eimers entnommen, sodass die Daten, die zuerst in den Stapel gelegt werden, immer auch die letzten sind, die entnommen werden. Die Warteschlange ist wie eine Wasserleitung. Die ersten Daten, die in die Warteschlange gestellt werden, fließen als erstes am anderen Ende der Warteschlange heraus.

In JavaScript ist die Ausführung einer Funktion ein typischer Prozess des Schiebens und Knallens des Stapels:

function fun1() {
    function fun2() {
        function fun3() {
            console.log('do it');
        }
        fun3();
    }
    fun2();
}
fun1();

Wenn das Programm ausgeführt wird, werden fun1, fun2 und fun3 zuerst auf den Stapel geschoben Nacheinander und dann Beim Aufruf einer Funktion wird zuerst fun3 aufgerufen (gepoppt), dann fun2 und fun1. Stellen Sie sich vor, wenn fun1 zuerst vom Stapel entfernt wird, gehen die Funktionen fun2 und fun3 verloren.

Single-Threaded und asynchron

In der Sprache von JavaScript ist das Programm Single-Threaded und hat nur einen Haupt-Thread. Weil es nicht schwer ist, sich vorzustellen, dass JavaScript ursprünglich als Skriptsprache konzipiert wurde, die im Browser ausgeführt wird. Wenn es für Multithreading ausgelegt ist und zwei Threads gleichzeitig das DOM ändern, wessen Entscheidung wird dann verwendet? Daher ist JavaScript Single-Threaded. In einem Thread wird der Code Satz für Satz abgearbeitet, bis das Programm fertig ist. Wenn in der Mitte zeitaufwändigere Vorgänge stattfinden, können Sie nur warten.

Das Single-Thread-Design macht die Ausführungseffizienz der Sprache sehr schlecht. Um die Leistung von Multi-Core-CPUs zu nutzen, unterstützt die JavaScript-Sprache asynchronen Code, wenn es zeitaufwändigere Vorgänge gibt , die Aufgabe kann für die asynchrone Ausführung geschrieben werden. Wenn die asynchrone Aufgabe nicht ausgeführt wurde, hält der Hauptthread die asynchrone Aufgabe an und führt die nachfolgende synchrone Aufgabe weiter aus. Schauen Sie dann zurück und führen Sie sie erneut aus asynchrone Aufgabe, deren Ausführung abgeschlossen ist.

Diese Art der Codeausführung passt tatsächlich zu vielen Szenen in unserem Leben. Zum Beispiel kommt Xiao Ming von der Arbeit nach Hause und möchte Wasser für Tee kochen Bei der synchronen Ausführungsmethode kocht er Wasser. Wenn das Wasser nicht kocht, wartet Xiao Ming wie ein Idiot und wartet darauf, dass das Wasser kocht, bevor er Tee zubereitet. Bei asynchroner Ausführung beginnt Xiao Ming zuerst, das Wasser zu kochen Dann geht er anderen Dingen nach, zum Beispiel fernsehen oder Musik hören. Warten Sie, bis das Wasser kocht, bevor Sie Tee kochen. Offensichtlich ist die zweite asynchrone Methode effizienter.

Was sind die häufigsten asynchronen Vorgänge? Es gibt viele, wir können ein paar gängige auflisten:

  • Ajax

  • DOM-Ereignisbetrieb

  • setTimeout

  • Promise's then-Methode

  • Knoten-Lesedatei

Wir nehmen zuerst eine Schauen Sie sich einen Code an:

//示例1
console.log(1);
setTimeout(function () {
    console.log(2);
}, 1000);
console.log(3);

Dieser Code ist sehr einfach. Fügen Sie ihn in den Browser ein und führen Sie ihn aus. Das Ergebnis ist wie folgt:

1
3
2

Weil die setTimeout-Funktion die Ausführung um 1000 verzögert Millisekunden gibt es zuerst 1 und 3 aus und nach 1000 Millisekunden wird 2 ausgegeben, was sehr logisch ist.

Lassen Sie uns den Code leicht ändern und die Verzögerungszeit von setTimeout auf 0 ändern:

//示例2
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0); //0毫秒,不延时
console.log(3);

Laufende Ergebnisse:

1
3
2

Warum ist die Verzögerung von 0 Millisekunden immer noch die letzte Ausgabe von 2? Keine Sorge, schauen wir uns einen Teil des Codes an:

//示例3
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0);
Promise.resolve().then(function(){
    console.log(3);
});
console.log(4);

Laufendes Ergebnis:

1
4
3
2

Für die oben genannten drei Codeteile, wenn Sie die Ergebnisse richtig schreiben und erklären können, warum das so ist Ausgabe wie diese, erklären Sie die JavaScript-Ereignisschleife sehr gut. Wenn Sie es nicht erklären können, sprechen wir darüber, was hier vor sich geht. Es ist tatsächlich sehr interessant.

Wie wird Javascript ausgeführt?

Lassen Sie uns zunächst kurz über die grundlegende Datenstruktur sprechen. Hat das etwas mit der Ereignisschleife zu tun, über die wir jetzt sprechen? Natürlich gibt es das. Das erste, was klargestellt werden muss, ist, dass der gesamte Javascript-Code auf dem Stapel ausgeführt wird , dies muss klar sein.

Wir können Code im Allgemeinen in synchronen Code und asynchronen Code unterteilen. Tatsächlich kann asynchroner Code weiter in zwei Kategorien unterteilt werden: Makroaufgaben und Mikroaufgaben.

Machen Sie sich keine Gedanken darüber, was Makroaufgaben und Mikroaufgaben sind. Oft sind solche hochrangigen Begriffe für unser Verständnis nicht förderlich: Makro bedeutet Makro und Mikro klein. .

Javascript ist eine interpretierte Sprache und ihr Ausführungsprozess ist wie folgt:

  1. 从上到下依次解释每一条js语句

  2. 若是同步任务,则压入一个栈(主线程);如果是异步任务,就放到一个任务队列里

  3. 开始执行栈里的同步任务,直到将栈里的所有任务都走完,此时栈清空了

  4. 回过头看异步队列里如果有异步任务完成了,就生成一个事件并注册回调,压入栈中

  5. 再返回第3步,直到异步队列都清空,程序运行结束

语言描述的费劲,不如看图:

Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

通过以上的步骤可以看到,不论是同步还是异步,只要是执行的时候都是要在栈里执行的,而一遍又一遍的回头检查异步队列,这种执行方式 就是所谓的“事件环”。

明白了javascript的执行原理,我们就不难理解之前的第二段代码,为什么setTimeout为0时会最后执行,因为setTimeout是异步代码,必须要等所有的同步代码都执行完,才会执行异步队列。即使setTimeout执行得再快,它也不可能在同步代码之前执行。

浏览器中的事件环

聊了这么多,我们好像还没有说宏任务和微任务的话题呢,上面说了,异步任务又分为微任务和宏任务,那它们又是一个怎样的执行机制呢?

注意!微任务和宏任务的执行方式在浏览器和Node中有差异,有差异!重要的事我们多说几遍,以下我们讨论的是在浏览器的环境里。

在浏览器的执行环境中,总是先执行小的、微任务,再执行大的、宏任务,回过头再看看第三段代码,为什么Promise的then方法在setTimeout之前执行?其根本原理就是因为Promise的then方法是一个微任务,而setTimeout是一个宏任务。

接下来我们借用阮一峰老师的一张图来说明:

Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

其实,以上这张图示我们可以再将它细化一点,这个图上的异步队列只画了一个,也就是说没有区分微任务队列和宏任务队列。我们可以脑补一下,在此图上多加一个微任务队列,当javascript执行时再多加一个判断,如果是微任务就加到微任务队列里,宏任务就加到宏任务队列里,在清空队列时,浏览器总会优先清空“微任务”。这样就把浏览器的事件环撤底说全了。

最后来一个大考,以下代码的运行结果是什么:

<script>
    setTimeout(function () {
        console.log(1);
        Promise.resolve().then(function () {
            console.log(2);
        });
    });
    setTimeout(function () {
        console.log(3);
    });
    Promise.resolve().then(function () {
        console.log(4);
    });
    console.log(5);
</script>

将此代码拷到chrome中跑一下,结果是:

5
4
1
2
3

不妨我们试着分析一下为什么是这个结果,首先输出5,因为console.log(5)是同步代码,这没什么可说的。

之后将前两个setTimeout和最后一个Promise放入异步队列,注意它们的区分,此时执行完了同步代码之后发现微任务和宏任务队列中都有代码,按浏览器的事件环机制,优先执行微任务,此时输出4。

然后执行宏任务队列里的第一个setTimeout,输出1。

此时,setTimeout中又有一个Promise,放入微任务队列。

再次清空微任务队列,输出2。

最后宏任务队列里还有最后一个setTimeout,输出3。

Node中的事件环

而Node中的事件环又和浏览器有些许的不同,在node.js的官方文档中有专门的描述,其中文档中有一张图,详细的说明了它的事件环机制,我们把它拿出来:

Wie versteht man die Javascript-Ereignisschleife? (Bilder und Text)

可以看到,node.js中的事件环机制分为了6个阶段,其中最重要的3个阶段我在上面做了注明:

  • timer阶段,指的就是setTimeout等宏任务

  • poll轮询阶段,如读取文件等宏任务

  • check阶段,setImmediate宏任务

图中每一个阶段都代表了一个宏任务队列,在Node事件环中,微任务的运行时机是在每一个“宏任务队列”清空之后,在进入下一个宏任务队列之间执行。这是和浏览器的最大区别。

还是用代码说话吧,有一道经典的Node.js事件环面试题:

const fs = require('fs');

fs.readFile('./1.txt', (err, data) => {
    setTimeout(() => {
        console.log('timeout');
    });
    setImmediate(() => {
        console.log('immediate');
    });
    Promise.resolve().then(() => {
        console.log('Promise');
    });
});

运行结果:

Promise
immediate
timeout

代码并不复杂,首先使用fs模块读取了一个文件,在回调的内部有两个宏任务和一个微任务,微任务总是优于宏任务执行的,因此先输出Promise。

但是之后的区别为什么先输出immdiate?原因就在于fs读取文件的宏任务在上图中的第4个轮询阶段,当第4个阶段清空队列之后,就该进入第5个check阶段,也就是setImmediate这个宏任务所在的阶段,而不会跳回第1个阶段,因此先输出immedate。

尾巴

最后总结一下,分析完浏览器和Node的事件环发现它们并不简单,但只要记住了它们之间的区别就可以分析出结果。

浏览器事件环是运行完一个宏任务马上清空微任务队列
Node事件环是清空完一个阶段的宏任务队列之后再清空微任务队列

最后,总结一下常见的宏任务和微任务:

宏任务 微任务
setTimeout Promise的then方法
setInterval process.nextTick
setImmediate MutationObserver
MessageChannel

相关推荐:

细说JavaScript事件循环机制-第二讲

javascript实现线程以及事件循环详解

Das obige ist der detaillierte Inhalt vonWie versteht man die Javascript-Ereignisschleife? (Bilder und Text). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn