udemyのガチで学びたい人のためのJavascriptメカニズムという講座を受講しまして、
自分の言葉でまとめたいなと思ったのでこの記事を書きました。
最近エンジニアになったばかりですので暖かい目で見ていただければと思います
jsを学ぶ理由
シンプルに「Reactを使ってみたいなー」と思ったからです
しかし、どうやらReactを使うにはjsの基礎がわかっていないと話にならんとのことだったので、とりあえず素のjsしっかり学ぼうと思いました。教材に触れる前の自分のレベル感としては「プログラミングの基礎はある程度わかる」「DOM操作も何となくわかる」「非同期処理はJQueryを使ってならやったことある」です
ちなみに知らなかった用語としては「スコープ」「クロージャー」「モジュール」などです
(スコープに関しては今まで意識して使ったことがないというのが正しいかもです)
Reactに必要な知識
こちらの記事を参考してに学ぶ項目を決めました
キーワードをざっと羅列するとスコープ、モジュール、非同期処理、スプレッド演算子、三項演算子、ES6クラス etc...
この教材のレベル感
正直なかなか難しいです
メモリレベルでのお話や、ブラックボックスになっている処理をあえて解き明かしたりと、
とても深い領域まで解説されています
一応講座自体は全て目は通しているのですが、とても難しくて重要度が高くないと判断した箇所に関しては、後で出てきた際に 「あ、なんか見たことある〜」 程度の理解で十分かなと思っています
ですので、「そこまではいらんかな〜」と感じたところはバリバリ端折るので許してください
非同期処理
JSでは同期処理と非同期処理を記述することができます
同期処理とは メインスレッドでコードが順番に実行される処理のこと
非同期処理とは 一時的にメインスレッドから処理が切り離される ことを指します
そもそもスレッドとは?
スレッドとは 連続して実行される一本の処理の流れ のことを指します
JSにはさまざまなスレッドがあり、この中でよく使われるのが メインスレッド です
・メインスレッド
・サービスワーカー
・Webワーカー
etc ...
メインスレッドの役割
メインスレッドでは 主に JSの実行とレンダリング(画面描写処理) が行われています
まあ、「なんかめっちゃ忙しそうな場所」 なんだなという認識で大丈夫です
非同期処理を実現するAPIやイベント
以下のAPIやイベントを使うと一時的に、メインスレッドが解放されて他の処理が実行可能になります
・setTimeout
・Promise
・queueMicrotask
・clickイベント
etc ...
今回はsetTimeoutを用いたコードを見てみましょう
// 引数に指定した時間だけメインスレッドを占有する関数
function sleep(ms) {
const startTime = new Date();
while (new Date() - startTime < ms);
console.log('sleep done');
}
//ボタンをクリックすると button clicked と出力される
const btn = document.querySelector('button');
btn.addEventListener('click', function(){
console.log('button clicked');
});
//2秒待った後に3秒間メインスレッドを占領する処理
setTimeout(function() {
sleep(3000)
}, 2000)
上記の画面をリロードしてすぐに、画像のボタンを連打すると自分の場合こうなりました
7 button clicked
sleep done
16 button clicked
簡単に流れを説明すると
- setTimeoutが走る2秒間でボタンを7回クリックした
- setTimeoutの処理が終わりsleep関数が実行され3秒間メインスレッドが占領される
- 3秒間は処理がある場所(タスクキュー)で保管されsleep関数の終了の合図が表示
- コンソールに3秒の間にクリックした数(自分の場合は16)が表示
このように、setTimeoutは非同期的な処理を実現するAPIだということがわかりますね
一言メモ
んで 「非同期処理を使うメリットてなんなん?」
結論から言うと最大の理由は UXの向上 らしいです
めちゃ忙しいメインスレッドから一時的に処理を引き離すことで
画面がフリーズしたりする地獄を回避できるそう
グーグルマップとかがいい例だそうな
なるほどd(´ω`)
Promise
Promiseとは 非同期処理をより簡単に、可読性が上がるように書けるようにしたもの です
実はコールバック関数を使うことで非同期処理を記述することができるのですが、コールバック関数をつなげた際にコードの階層が深くなり可読性が悪くなるというデメリットがあります
ちなみにコールバック関数を用いて、階層が深くなりすぎたコードがこちら
function sleep(callback, val) {
setTimeout(function() {
console.log(val++);
callback(val);
}, 1000);
}
sleep(function(val) {
sleep(function(val) {
sleep(function(val) {
sleep(function(val) {
}, val);
}, val);
}, val);
}, 0);
oh...
2度と見たくないですね
こんなコードが実務で出てきたら、ディスプレイを破壊したくなる人が大量発生することでしょう
早速そんな地獄を回避するためにpromiseを用いたサンプルコードを見ていきましょう
new Promise(function(resolve, reject) {
console.log('promise');
resolve('hello');
}).then(function(data) {
console.log('then:' + data);
return data;
}).then(function(data) {
console.log('then:' + data);
return data;
}).catch(function(data) {
console.log('catch:' + data);
}).finally(function() {
console.log('finally');
})
console.log('global end');
上記のコードを簡単に説明すると
-
resolve()が呼ばれるとthenの中の処理が実行され、
reject()が実行されるとcatchの中の処理が実行されます -
今回はresolve()が呼ばれているため最初のthen中のconsole.log()が実行されます
-
次にもう一つのthen内の処理が実行されます
-
thenまたはcatch内の処理が全て終わると最後にfinallyの処理が実行されます
ちなみに実行結果は以下の通りです
1 promise
2 global end
3 then:hello
4 then:hello
5 finally
こちらの流れも説明すると
- then、catch、finally内の処理は非同期で処理されるため、初めにpromiseがコンソールに表示される
- 次にthenはresolveをcatchはrejectが実行されるのを待つため先にglobal endが表示される
- そして最後にthenの処理が実行される
この書き方だとthenをたくさんつなげても階層が深くならずに済むのでとても良きですね!
Promiseチェーン
Promiseを用いて非同期処理を順番に実行したいことがあるらしいです
早速コードを見てみましょう
function sleep(val) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(val++);
resolve(val);
}, 1000);
});
}
sleep(0).then(function(val) {
return sleep(val);// ①
}).then(function(val) {
return sleep(val);// ①
});
ここでのポイントはthenのコールバック関数のreturnにsleep関数の戻り値、すなわちPromiseのインスタンスが返っている点です- ①
処理をつなげるには必ずPromiseのインスタンスが返ることが大事で、これが途切れると順に処理が実行されなくなります
これは結構大事なことらしいです (´ω`)
asyncとawait
asyncとawaitは Promiseをさらに直感的に記述できるようにしたもの です
asyncは Promiseを返却する関数の宣言を行うため に用いられ、awaitは Promiseを返却する関数の非同期処理が完了するのを待って次の処理に移行するように制御を加えます
一言メモ
asyncは非同期の関数を定義できるキーワード
awaitはPromiseを受け取るまで待機する演算子
それらを使ったコードはこちらです
//返り値がPromiseの関数
function sleep(val) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(val++);
resolve(val);
}, 1000);
});
}
// 先頭にAsyncをつけた関数
async function init() {
let val = await sleep(0);
val = await sleep(val);
val = await sleep(val);
val = await sleep(val);
val = await sleep(val);
console.log(val);
}
init(); //実行結果 1 2 3 4 5
流れを簡単に説明すると
- init関数の内部で、awaitで値を受け取りたいため先頭にasyncキーワードをつけときます
(awaitはasyncで宣言した関数のスコープ内でしか使えないため) - sleep関数の返り値はPromiseのインスタンスなので、awaitを付与した状態で変数valに代入します
- 同様の処理を数回記述することで、Promiseのチェーンが切れることなく処理が走ります
- 最後にvalの値をコンソールに出力します
ちなみに、asyncを付与した関数はreturn文を記述しなくても返り値としてPromiseを返します
fetch
fetchとは 非同期でサーバからデータを取得する際に用いるメソッド です
これもコードで動きを確認してみましょう
[
{
"name": "Bob",
"age": 23
},
{
"name": "Tim",
"age": 30
},
{
"name": "Sun",
"age": 25
}
]
今回は上記のjsonファイルを読み込んで使うとします
次に記述するjsファイルと同じフォルダ内にあると思ってください
fetch('users.json').then(function(response) {
return response.json();
}).then(function(json) {
for(const user of json) {
console.log(`I'm ${user.name}, ${user.age} years old`)
}
});
上記のコードの流れを確認すると
- fetchはPromiseを返すのでthenを使って繋ぐことができます
- 2行目で取得してきたjsonデータをreturnします
(responseはオブジェクトであり、サーバから返ってきたさまざまな情報を格納しています) - そしてそれをthenでつなぎ、for文を用いて表示しています
といった流れです
responseオブジェクトにはさまざまな情報が入っており
サーバーとの通信ができたかを確認するデータや、上記のように取得したjsonデータなどさまざまです
fetchはReactでも重要な項目らしいので絶対抑えるべきポイントですね!
終わりに
今回は非同期処理編として学習したことをまとめました
今後はReactやLaravelについて使ってみた(学んでみた)系の記事を書きたいなと思っているので
もしよければそちらもみてください!