序文
2925ワードの記事は、読書は10分程度かかります。
要約:非同期と同期のタグの間の差は実行この論文のレビュー、コンセプトJavascriptのイベントループ、マイクロタスクキュータスクキュー。
- オリジナル住所:非同期JavaScriptを理解します
- いいえ公共:「フロントエンド高度な学習」、回答、」666」は、フロントエンド技術の本のパッケージを取得していません
私は人々が成功したことはありませんが失敗することはありませんことを恐れています。
Javascriptがシングルスレッドプログラミング言語、一つだけを行うことができ、同時に単一スレッドです。JavaScriptエンジンであるプログラミング言語、それを上に置く(仮想マシン内のJavaScriptコードを実行する)だけ同時に声明で行うことができます。
シングルスレッドの言語の利点は、あなただけの同時実行の問題を気にせずに書くということです。しかし、それはまた、メインスレッドをブロックせずに、このようなネットワーク要求の場合のように、いくつかの操作を実行するのに長い時間のために行くことができないことを意味します。
我々はインターフェイスからいくつかのデータを要求した場合、サーバがデータを返すためにいくつかの時間を必要と想像の下では、それが応答しない状態でメインスレッドのページをブロックします。
これは、非同期Javascriptが我々が(例えば、コールバック、約束および非同期/のawaitなど)の非同期操作を介してメインスレッドをブロックせずに長いネットワーク要求を行うことができ、入って来ています。
理解がすべてのこれらの概念は、必ずしもあなたがすぐに優れたJavaScript開発者になることはありませんが、非同期理解する参考になります。
いくつかの単語の男は、身体の開始を言った:)
どのように同期コードが実行されます
非同期JavaScriptの研究掘り下げる前に、我々は最初の同期コードをJavaScriptエンジンで実行される方法を見て。例を見てください:
const second = () => {
console.log('Hello there!');
}
const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}
first();
上記のコードは、JavaScriptエンジンで実行される方法を理解するために、我々はして行かなければならないのJavascriptの実装と実行スタックのコンテキストを理解します。
実行コンテキスト
いわゆる実行コンテキストは、抽象的な概念Javascriptのコードの実行環境です。JavaScriptコードが実行コンテキストで実行されます。
内部関数のコードは、関数の実装のコンテキストで実行される、グローバル・コードは、グローバル実行コンテキストにおいて実行される、各機能は、独自の実行コンテキストを有します。
実行スタック
名前は、実行スタックを意味するように実行されるコードのすべての格納された実行コンテキストを作成するために使用される最後の最初のアウト(LIFO)スタック構造です。
シングルスレッドの理由、それはこれだけスタックまたは削除の実行コンテキストの上からスタックベースの構造を追加しているため、Javascriptが、一つだけ実行スタックです。
JavaScriptエンジンがそれらを実行する方法を理解しようと、私たちは上記のコードに戻りましょう。
const second = () => {
console.log('Hello there!');
}
const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}
first();
それでは次に、ここで起こったのか?
コードが実行されると、(ここで使用された最初のグローバル実行コンテキストmain()
図示)を作成し、実行スタックの頂部に押圧されます。実装する際first()
のコード行は、その実行コンテキストは、実行スタックの最上部に押し付けられます。
次いで、console.log('Hi there!');
実行コンテキストの実行が実行スタックからポップされた後に実行スタックの機能の実行コンテキストは、上面に押し付けられます。呼び出す次にsecond()
機能を、機能実行コンテキストは実行スタックの最上部に押し込まれます。
その後、実行console.log('Hello there!');
対応する機能の実行コンテキストは、実行終了をポップされた実行スタックにプッシュされ、及びsecond()
機能実行の終わりには、実行コンテキストが吐出されます。
console.log(‘The End’)
場合実行は、関数の実行コンテキストは、実行の終了が排出され、実行スタック上にプッシュされるfirst()
機能実行端部は、対応する実行コンテキストが吐出されます。
プログラムを通して終了し、グローバル実行コンテキスト(メイン())が排出されます。
どのように非同期コードを実行されます
今、私たちは同期コードの実装の基本的な理解を持っていること、のはどのように実行されるか非同期コードを見てみましょう:
おもり
我々は要求またはネットワーク要求の一般的な絵を開始するために、同期方法を使用すると仮定し、次のような例があります:
const processImage = (image) => {
/**
* doing some operations on image
**/
console.log('Image processed');
}
const networkRequest = (url) => {
/**
* requesting network resource
**/
return someData;
}
const greeting = () => {
console.log('Hello World');
}
processImage(logo.jpg);
networkRequest('www.somerandomurl.com');
greeting();
絵のリクエストやネットワークを要求するには時間がかかりますので、私たちが呼ぶときprocessImage()
の時間を、それにかかる時間は、画像のサイズによって異なります。
場合processImage()
関数の端部は、実行コンテキストに応答して実行されているスタックからポップされ、その後、呼び出しnetworkRequest()
関数を、対応する実行コンテキストが実行スタックにプッシュされ、同様の機能を終了する時間がかかる場合があります。
networkRequest()
函数执行结束,调用greeting()
,然后里面只有一行console.log('Hello World')
,``console.log()函数通常执行会很快,因此
greeting()`会很快执行完然后返回结果。
可以发现,我们必须等函数(比如processImage,networkRequest函数)执行结束才能调用下一个函数。这意味着这些函数调用的时候会阻塞主线程,造成主线程不能执行其他代码,这是我们所不希望的。
所以怎么解决这个问题呢?
最简单的解决办法就是使用异步的回调函数,有了异步的回调函数就不会阻塞主线程,看例子:
const networkRequest = () => {
setTimeout(() => {
console.log('Async Code');
}, 2000);
};
console.log('Hello World');
networkRequest();
这里我们使用了setTimeout
方法去模拟网络请求函数。
请注意:setTimeout
不是Javascript引擎提供的,而是web API(浏览器中)和C/C++ API(nodejs中)的一部分。
事件循环、Web API和消息队列/任务队列并不是Javascript引擎的一部分而是浏览器的Javascript运行环境或是Nodejs的Javascript运行环境的一部分,在Nodejs中,Web API被C/C++ API替代。
回到上面的代码,看看异步的代码是如何执行的:
const networkRequest = () => {
setTimeout(() => {
console.log('Async Code');
}, 2000);
};
console.log('Hello World');
networkRequest();
console.log('The End');
代码开始执行,console.log(‘Hello World’)
函数的执行上下文首先被压入执行栈,执行结束后被弹出,然后调用networkRequest()
,对应的函数执行上下文被压入执行栈。
紧接着 setTimeout()
函数被调用,对应的函数执行上下文被压入执行栈。
setTimeout
有两个参数:1. 回调函数;2. 时间(以毫秒ms为单位);3. 附加参数(会被传到回调函数里面)
setTimeout()
函数会在web API运行环境中进行一个2s
的倒计时,这个时候 setTimeout()
函数就已经执行完了,执行上下文从执行栈中弹出。再然后console.log('The End')
函数被执行,进入执行栈,结束后弹出执行栈。
这时候倒计时到期,setTimeout()
的回调函数被推到消息队列中,但回调函数不会立即执行,这是事件循环开始的地方。
事件循环
事件循环的工作就是去查看执行栈,确定执行栈是否为空,如果执行栈为空,那么就去检查消息队列,看看消息队列中是否有待执行的回调函数。它按照类似如下的方式来被实现:
while (queue.waitForMessage()) {
queue.processNextMessage();
}
在这里,执行栈已经为空,消息队列包含一个setTimeout
函数的回调函数,因此事件循环把回调函数的执行上下文压入执行栈的顶端。
然后console.log(‘Async Code’)
函数的执行上下文被压入执行栈,结束后从执行栈弹出。这时候回调函数执行结束,对应的执行上下文也从执行栈中弹出。
DOM事件
**消息队列(也叫任务队列)**中也会包含来自DOM事件(比如点击事件,键盘事件等),看例子:
document.querySelector('.btn').addEventListener('click',(event) => {
console.log('Button Clicked');
});
对于DOM事件来说,web API中会有一个事件侦听器坚挺某个事件被触发(在这里是click事件),当某个事件被触发时,就会把相应的回调函数放入消息队列中执行。
事件循环再次检查执行栈,如果执行栈为空,就把事件的回调函数推入执行栈。
我们已经了解了异步回调和事件回调是如何执行的,这些回调函数被存储在消息队列中等待被执行。
ES6任务队列和微任务队列
ES6中为promise
函数引入了微任务队列(也叫作业队列)的概念。微任务队列和消息队列的区别就是优先级上的区别,微任务队列的优先级要高于消息队列。也就是说在微任务队列的promise
回调函数会比在消息队列中的回调函数更先执行。
比如:
console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
new Promise((resolve, reject) => {
resolve('Promise resolved');
}).then(res => console.log(res))
.catch(err => console.log(err));
console.log('Script End');
输出:
Script start
Script End
Promise resolved
setTimeout
可以看到promise
是在setTimeout
之前执行的,因为promise
的response被存储在微任务队列中,有比消息队列更高的优先级。
再看另一个例子,有两个promise
函数,两个setTimeout
函数:
console.log('Script start');
setTimeout(() => {
console.log('setTimeout 1');
}, 0);
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
new Promise((resolve, reject) => {
resolve('Promise 1 resolved');
}).then(res => console.log(res))
.catch(err => console.log(err));
new Promise((resolve, reject) => {
resolve('Promise 2 resolved');
}).then(res => console.log(res))
.catch(err => console.log(err));
console.log('Script End');
输出:
Script start
Script End
Promise 1 resolved
Promise 2 resolved
setTimeout 1
setTimeout 2
可以看到两个promise
的回调函数都在setTimeout
的回调函数之前运行,因为相比消息队列事件循环会优先处理微任务队列中的回调函数。
当事件循环处理微任务队列中的回调函数的时候另一个promise
被resolved了,然后这个promise
的回调函数会被添加到微任务队列中。并且它会被优先执行,无论消息队列中的回调函数的执行会花费多长时间,都要排队。
比如:
console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
new Promise((resolve, reject) => {
resolve('Promise 1 resolved');
}).then(res => console.log(res));
new Promise((resolve, reject) => {
resolve('Promise 2 resolved');
}).then(res => {
console.log(res);
return new Promise((resolve, reject) => {
resolve('Promise 3 resolved');
})
}).then(res => console.log(res));
console.log('Script End');
打印:
Script start
Script End
Promise 1 resolved
Promise 2 resolved
Promise 3 resolved
setTimeout
したがって、すべてのマイクロタスクキューのコールバック関数は、になりますメッセージキューのコールバック関数の前に実行されます。換言すれば、イベントループは、第クリアされたマイクロタスクキューの意志実行するコールバック関数をメッセージキューコールバック関数。
結論
我々は、実行だけでなく、(実行スタックを含め、イベントループ、マイクロタスクキュー、メッセージキューなど)いくつかの他の概念どのように同期および非同期でJavaScriptコードを理解しています。
上記。
限られた容量、一般のレベルは、感謝される、正誤表を歓迎しました。
より多くの記事は、公開番号「フロントエンド高度な学習」、回答、」666」を懸念する購読、フロントエンド技術の本のパッケージを取得