Heim > Artikel > Web-Frontend > Verstehen Sie den JavaScript-Ausführungsmechanismus gründlich
Der Zweck dieses Artikels besteht darin, sicherzustellen, dass Sie den Ausführungsmechanismus von Javascript gründlich verstehen. Wenn Sie nach dem Lesen dieses Artikels immer noch nicht verstehen, können Sie mich schlagen.
Ob Sie ein JavaScript-Neuling oder ein erfahrener Mensch sind, ob Sie ein Vorstellungsgespräch führen oder tägliche Entwicklungsarbeit leisten, wir stoßen oft auf Situationen wie diese: Wenn wir ein paar Codezeilen haben, müssen wir die Ausgabe kennen Inhalt und Reihenfolge. Da es sich bei Javascript um eine Single-Threaded-Sprache handelt, können wir die Schlussfolgerung ziehen:
Die Leser werden überrascht sein, wenn sie das sehen Lao: Weiß ich nicht, dass js Zeile für Zeile ausgeführt wird? Muss man das überhaupt sagen? Seien Sie geduldig, denn js wird Zeile für Zeile ausgeführt, daher denken wir, dass js so ist:
let a = '1';console.log(a);let b = '2';console.log(b);复制代码
Tatsächlich ist js jedoch so:
setTimeout(function(){ console.log('定时器开始啦') });new Promise(function(resolve){ console.log('马上执行for循环啦'); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); } }).then(function(){ console.log('执行then函数啦') });console.log('代码执行结束');复制代码
Gemäß dem Konzept, dass js in der Reihenfolge ausgeführt wird, in der Anweisungen erscheinen , habe ich die Ausgabe selbstbewusst aufgeschrieben Ergebnisse:
rrreeIch bin zu Chrome gegangen, um es zu überprüfen, und das Ergebnis war völlig falsch. Ich war einen Moment verwirrt. Habe ich es Zeile für Zeile ausgeführt, wie versprochen?
Wir müssen den Ausführungsmechanismus von Javascript wirklich gründlich verstehen.
Javascript ist eine Single-Threaded- Web-Worker-Sprache, aber der Kern von Javascript ist, dass es Single ist -Gewindet. Unverändert. Daher werden alle JavaScript-Versionen von „Multithreading“ mit einem einzigen Thread simuliert, und alle JavaScript-Multithreads sind Papiertiger!
Da js Single-Threaded ist, ist es wie eine Bank mit nur einem Fenster. Kunden müssen sich anstellen, um Geschäfte einzeln zu erledigen müssen einzeln ausgeführt werden. Dauert eine Aufgabe zu lange, muss auch die nächste Aufgabe warten. Hier stellt sich also die Frage: Wenn wir Nachrichten durchsuchen möchten, die in den Nachrichten enthaltenen ultrahochauflösenden Bilder jedoch sehr langsam geladen werden, sollte unsere Webseite dann hängen bleiben, bis die Bilder vollständig angezeigt werden? Daher teilen kluge Programmierer Aufgaben in zwei Kategorien ein:
Wenn wir die Website öffnen, erfolgt der Rendering-Prozess des Webs Seite Es handelt sich um viele Synchronisierungsaufgaben, z. B. das Rendern des Seitengerüsts und der Seitenelemente. Aufgaben, die viele Ressourcen beanspruchen und lange dauern, wie zum Beispiel das Laden von Bildern und Musik, sind asynchrone Aufgaben. Für diesen Teil gibt es eine strenge Textdefinition, aber der Zweck dieses Artikels besteht darin, den Ausführungsmechanismus mit minimalem Lernaufwand gründlich zu verstehen. Daher verwenden wir zur Veranschaulichung eine Karte:
Wenn der in der Karte auszudrückende Inhalt in Worten ausgedrückt wird:
Wir kommen nicht umhin zu fragen: Woher wissen wir, dass der Haupt-Thread-Ausführungsstapel leer ist? In der js-Engine gibt es einen Überwachungsprozess, der kontinuierlich prüft, ob der Haupt-Thread-Ausführungsstapel leer ist. Sobald er leer ist, wird er in die Ereigniswarteschlange verschoben, um zu prüfen, ob eine Funktion auf den Aufruf wartet.
Nachdem ich so viele Worte gesagt habe, ist es besser, einen Code zu schreiben, um es einfacher zu machen:
//"定时器开始啦"//"马上执行for循环啦"//"执行then函数啦"//"代码执行结束"复制代码
Das Obige ist ein einfacher ajax
Anforderungscode:
success
. console.log('代码执行结束')
. success
tritt in die Ereigniswarteschlange ein. success
aus der Event Queue und führt sie aus. Ich glaube, dass Sie durch den obigen Text und Code bereits ein vorläufiges Verständnis der Ausführungssequenz von js haben. Als nächstes studieren wir das fortgeschrittene Thema: setTimeout.
Das berühmte setTimeout
Der erste Eindruck davon ist natürlich, dass die asynchrone Ausführung verzögert werden kann in 3 Sekunden:
let data = []; $.ajax({ url:www.javascript.com, data:data, success:() => { console.log('发送成功!'); } })console.log('代码执行结束');复制代码
Nach und nach wird setTimeout
an mehr Stellen verwendet, und es treten auch Probleme auf. Manchmal wird geschrieben, dass die Verzögerung 3 Sekunden beträgt, aber tatsächlich dauert die Ausführung der Funktion 5 oder 6 Sekunden . Was ist los?
先看一个例子:
setTimeout(() => { task(); },3000)console.log('执行console');复制代码
根据前面我们的结论,setTimeout
是异步的,应该先执行console.log
这个同步任务,所以我们的结论是:
//执行console//task()复制代码
去验证一下,结果正确!
然后我们修改一下前面的代码:
setTimeout(() => { task() },3000) sleep(10000000)复制代码
乍一看其实差不多嘛,但我们把这段代码在chrome执行一下,却发现控制台执行task()
需要的时间远远超过3秒,说好的延时三秒,为啥现在需要这么长时间啊?
这时候我们需要重新理解setTimeout
的定义。我们先说上述代码是怎么执行的:
task()
进入Event Table并注册,计时开始。sleep
函数,很慢,非常慢,计时仍在继续。timeout
完成,task()
进入Event Queue,但是sleep
也太慢了吧,还没执行完,只好等着。sleep
终于执行完了,task()
终于从Event Queue进入了主线程执行。上述的流程走完,我们知道setTimeout
这个函数,是经过指定时间后,把要执行的任务(本例中为task()
)加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。
我们还经常遇到setTimeout(fn,0)
这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?
答案是不会的,setTimeout(fn,0)
的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明:
//代码1console.log('先执行这里'); setTimeout(() => { console.log('执行啦') },0);复制代码
//代码2console.log('先执行这里'); setTimeout(() => { console.log('执行啦') },3000);复制代码
代码1的输出结果是:
//先执行这里//执行啦复制代码
代码2的输出结果是:
//先执行这里// ... 3s later// 执行啦复制代码
关于setTimeout
要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。有兴趣的同学可以自行了解。
上面说完了setTimeout
,当然不能错过它的孪生兄弟setInterval
。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval
会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。
唯一需要注意的一点是,对于setInterval(fn,ms)
来说,我们已经知道不是每过ms
秒会执行一次fn
,而是每过ms
秒,会有fn
进入Event Queue。一旦setInterval
的回调函数fn
执行时间超过了延迟时间ms
,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。
传统的定时器我们已经研究过了,接着我们探究Promise
与process.nextTick(callback)
的表现。
Promise
的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)
类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:
不同类型的任务会进入对应的Event Queue,比如setTimeout
和setInterval
会进入相同的Event Queue。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用文章最开始的一段代码说明:
setTimeout(function() { console.log('setTimeout'); })new Promise(function(resolve) { console.log('promise'); }).then(function() { console.log('then'); })console.log('console');复制代码
setTimeout
,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述)Promise
,new Promise
立即执行,then
函数分发到微任务Event Queue。console.log()
,立即执行。then
在微任务Event Queue里面,执行。setTimeout
对应的回调函数,立即执行。事件循环,宏任务,微任务的关系如图所示:
我们来分析一段较复杂的代码,看看你是否真的掌握了js的执行机制:
console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); })new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) })复制代码
第一轮事件循环流程分析如下:
console.log
,输出1。setTimeout
,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1
。process.nextTick()
,其回调函数被分发到微任务Event Queue中。我们记为process1
。Promise
,new Promise
直接执行,输出7。then
被分发到微任务Event Queue中。我们记为then1
。setTimeout
,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2
。宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
我们发现了process1
和then1
两个微任务。
process1
,输出6。then1
,输出8。好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1
宏任务开始:
process.nextTick()
,同样将其分发到微任务Event Queue中,记为process2
。new Promise
立即执行输出4,then
也分发到微任务Event Queue中,记为then2
。宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
process2
和then2
两个微任务可以执行。process.nextTick()
分发到微任务Event Queue中。记为process3
。new Promise
,输出11。then
分发到微任务Event Queue中,记为then3
。宏任务Event Queue | 微任务Event Queue |
---|---|
process3 | |
then3 |
process3
und then3
werden ausgeführt. Der gesamte Code verfügt über insgesamt drei Ereignisschleifen und die vollständige Ausgabe ist 1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12.
(Bitte beachten Sie, dass die Ereignisüberwachung in der Knotenumgebung von libuv abhängt und die Front-End-Umgebung nicht genau identisch ist und die Ausgabereihenfolge möglicherweise Fehler aufweist)
Wir haben von Anfang an gesagt, dass JavaScript eine Single-Threaded-Sprache ist, egal welches neue Framework oder welche neue Syntax Sugar die sogenannte Asynchronität implementiert Die Simulation mit synchronen Methoden ist sehr wichtig.
Ereignisschleife ist eine Methode für js, um Asynchronität zu erreichen, und ist auch der Ausführungsmechanismus von js.
Es gibt einen großen Unterschied zwischen der Ausführung und Ausführung von Javascript. Die Ausführungsmethoden von Javascript sind in verschiedenen Umgebungen unterschiedlich , wie Knoten, Browser, Ringo usw. von. Und das Ausführen bezieht sich hauptsächlich auf die Javascript-Parsing-Engine, die vereinheitlicht ist.
Es gibt viele Arten von Mikroaufgaben und Makroaufgaben, wie z. B. setImmediate
usw., und ihre Ausführung hat etwas gemeinsam. Interessierte Schüler können lernen alleine darüber.
Begreifen Sie die beiden Grundpunkte fest, konzentrieren Sie sich ernsthaft auf das Studium von JavaScript und verwirklichen Sie so schnell wie möglich den großen Traum, Front-End-Meister zu werden!
Das obige ist der detaillierte Inhalt vonVerstehen Sie den JavaScript-Ausführungsmechanismus gründlich. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!