blog.takurinton.dev

冷房壊れて2週間

2020-08-05

こんにちは

ハローエブリワン! 先日この記事書いてたら割と書き進めたところで下書き保存せずにブラウザ閉じて書いたもの全部消えちゃったので今日になって泣く泣く書き直してます。(他にも泣きたいことはたくさんあるのですが今回は割愛)

冷房が壊れて2週間経ちますが、だいぶきついのでそろそろ大家さんに相談しようと思ってます。

さて、今日の記事はVOYAGE GROUPのサマーインターンのTreasureに参加してるので初日を終えての学んだことと感想を簡単にですがまとめたいと思います。 ではいく!

初日は講義

初日はJavaSctiptの講義がありました。
午前中は歴史や発展の背景について。
午後は実際にコードに触れて理解を深めました。
フロントエンド得意ですとか言い張ってここまでやってきたんですが、見事にボコボコにされました。笑笑 あとはインターン生のレベルが高すぎてこれからが心配です。

講義内容について

具体的には

  • プリミティブ型とオブジェクト型
  • event loop
  • function関数とarrow関数
  • Promiseとasync/await

について学びました。それぞれについて少しずつ掘っていきます。

JavaScriptはちょっと癖のある言語なので頑張って理解しないと死ぬということを理解しました。あと、僕JavaScript理解してると思ってたんだけどな。。。

プリミティブ型とオブジェクト型

JavaSctiptにはプリミティブ型とオブジェクト型というものがあります。 分類については以下の表にまとめました。

種類
プリミティブ string, int(Bidint), boolean, undefined, symbole
オブジェクト その他

他の言語だとデータ構造として操作する系がオブジェクト型、その他がプリミティブ型といったところでしょうか。
JavaScriptではそこの違いで代入の挙動が変わってきます。

具体的には、プリミティブ型は値渡し、オブジェクト型は参照渡しになります。 例として以下のようなコードがあります。

// プリミティブ型
var a = 'hogehoge'
var b = a // 代入
b = 'fugafuga'

console.log(a) // => hogehoge (aの値は変わらない)
console.log(b) // => fugafuga


// オブジェクト型
var a = [1,2,3,4,5]
var b = a
b[0] = 10

console.log(a) // => [10,2,3,4,5]
console.log(b) // => [10,2,3,4,5] (bも一緒に変わる)

以上のようにプリミティブ型では代入は値渡しなので直接値が代入されますが、オブジェクト型では参照渡しになるので注意が必要です。 また、プリミティブ型にはラッパーオブジェクトという概念が存在していて、プリミティブ型からプロパティやメソッドにアクセスする場合、一時的にラッパーオブジェクトに変換されます。プリミティブ型はプロパティやメソッドを持たないのですが、ラッパーオブジェクトのおかげであたかもプロパティやメソッドがあるかのような振る舞いをしてくれます。

let name = 'takurinton'
console.log(name.length) // => 10

本来lengthというメソッドは持っていないのですが、なぜかlengthが正しく実行されている。これがラッパーオブジェクトなのです。 これは内部的にはこんな感じで実行されています。

let name = 'takurinton'
console.log(new String(name).length) // => 10

これは意識せずに使えるので便利ですね。分散システムの透過性みたい(黙れ)

event loop

JavaScriptはタスクをひたすら無限ループでこなす言語です。 JavaScriptはWebのフロントでよく使われてるのを見ますが、DOMの更新もJavaScriptのタスクの一つとなっています。

ここで質問、Twitterにいるツヨツヨの人たちは知ってると思うけど、スタックとキューはご存知でしょうか?( QueueQueue車 のツイートが流行りましたね、あれ大好きです)

JavaSctiptのタスクはキューに入れられて実行待ちの状態になります。そのキューからスタックを生成して処理をしていきます。

  • タスクがキューに溜まっていく
  • キューから取り出したタスクをスタックに入れて実行する
  • DOMのレンダリングもキューに入るタスクの一部として処理される
  • QueueQueue車のサイレンはFIFOFIFO ( 参考はこちら )

ここで、実際にブラウザでリアルタイムで更新されるコードを書いていきます。 現在のこのコードでは、1回1回レンダリングをしてくれるわけではないので更新をしてくれません。 ちなみに、loop()という関数の中で再帰的呼び出しているloop()は直接スタックに追加されていきます。一番外側(?)loop()1つのタスクですので。

<!DOCTYPE html>
<head>
    <title>event loop</title>
</head>
<body>
    <button id="heavyCount">click</button>
    <div id="counter">0</div>
    <script>
        const button = document.getElementById('heavyCount');
        const counter = document.getElementById('counter');
        button.addEventListener('click', function() {
            let count = 0;
            let times = 1000;
            function loop() {
                if (count++ < times) {
                    counter.innerHTML = count;
                    loop();
                } else {
                    alert("Done");
                }
            }
            loop();
        });
    </script>
</body>
</html>

これをスタックではなくキューに分散させて処理をしないとDOMは更新されません。 なぜならスタックが全てからになるとキューが実行されるので、loop()の再帰的に呼び出しているloop()が全部終わるまでDOMの値が更新されないからです。こやつらを分割して実行するにはsetTimeoutという非同期APIを使用して処理をしていきます。 そうすると、コードは以下のようになります。

<!DOCTYPE html>
<head>
    <title>event loop</title>
</head>
<body>
    <button id="heavyCount">click</button>
    <div id="counter">0</div>
    <script>
        const button = document.getElementById('heavyCount');
        const counter = document.getElementById('counter');
        button.addEventListener('click', function() {
            let count = 0;
            let times = 1000;
            function loop() {
                if (count++ < times) {
                    counter.innerHTML = count;
                    // loop(); 
                    setTimeout(loop, 0); // これを追加    
                } else {
                    alert("Done");
                }
            }
            loop();
        });
    </script>
</body>
</html>

以上のようにsetTimeoutを利用することで処理を分割して行うことができるため、DOMがしっかり更新されます。 しかし、以下の点に気をつけなければいけません。

  • 非同期処理なので最終的に全てのタスクは実行されるが順番がきれいになるかという保証はされていない
  • setTimeoutの第一引数の戻り値がWebAPIに送られ、第二引数秒後に処理が実行される
  • 第二引数はデフォルトでは0なので省略可能
  • WebAPIからキューに処理が移り、スタックで実行される
  • WebAPIは非同期APIなど別の呼び方もされるので注意が必要

これを行うことで1つ1つの処理が一度キューに送られてからスタックに行って実行されるのでDOMがしっかり更新されます。 JavaScriptは複数の関数が同時に実行されることはないため、このように非同期で明示的に処理をする必要があります。これは良くも悪くもで、やたら並列に処理をしてもめちゃくちゃになってわけがわからなくなってしまうこともあるのでうまく使おうねとしか言えません。 これらのことから、JavaScriptは並列処理ってより並行処理に近い実行方法なのかなと思います。

function関数とarrow関数

JavaScriptには関数の形式としてfunction関数とarrow関数の2種類の関数があります。 どちらも関数ですが、大きな違いがthisの挙動です。参照されるところが大きく違います。

タイプ thisの呼ばれるタイミング
function 実行された時
arrow 定義した時

という感じです。実際にコードにしてみます。

this.name = "hogehoge";
const obj = {
    name: "takurinton",
    function: function() {
        console.log("[function] this.name: " + this.name);
    },
    arrow: () => {
        console.log("[arrow] this.name: " + this.name);
    }
}
obj.function() // => takurinton
obj.arrow() // => hogehoge

といった感じになります。
function関数は普通に理解できますが、arrow関数は理解できないですね。arrow関数は定義した時に実行されるとのことですが、これは定義いした時にはまだ } が閉じ切ってないので未定義という扱いになります。 つまり

 }, 
arrow: () => {
        console.log("[arrow] this.name: " + this.name); // この時点ではまだobjが定義し終わってないのでその上のhogehogeを参照するといった具合です。
    }
}

このように実行のタイミングが違うことに注意しないといけません。 最近はarrow関数が主流になりつつあり、自分も乱用するのですが、実務でコードを書くときは新規開発だけするわけではないので古いコードも読まなければなりません。このような知識がないと死ぬ可能性100%なので注意して勉強しましょう!

Promiseとasync/await

最後がPromiseとasync/awaitの非同期処理についてです。

Promiseとは、複数の非同期処理を順番に実行する際に用いられます。 前の処理が終了してからどんどん次の処理に移って行きます。

new Promise((resolve, reject) => {
    console.log('takurinton1');
    resolve();
})
.then(() => {
    throw new Error('Something failed');
        
    console.log('error that');
})
.catch(() => {
    console.log('takurinton2');
})
.then(() => {
    console.log('takurinton3');
});

出力は以下のようになります。

takurinton1
takurinton2
takurinton3

このようにPromiseは順番をつける時に便利になります。

async/awaitも同様に非同期処理で利用されます。

(async function(){
    try {
        const result = await doAsyncTask1();
        const newResult = await doAsyncTask2(result);
        const finalResult = await doAsyncTask3(newResult);
        console.log('final result: ' + finalResult);
    } catch(error) {
        failureTask(error);
    }
})();

このような形でそれぞれの関数の引数を前の関数の戻り値にすることで順番に実行することができます。Promiseと一緒に利用することができます。 非同期処理については知識が浅く、上の他のやつより深く触れることができませんでしたが、なんとかコードの意味は理解できるようにはなりました!

まとめ

初日ということでしたが結構ボリューミーでこれの他にもSkyWayを使用して簡単なビデオチャットを作成したりそれを拡張して遊んだりしました。 チュートリアルまでは簡単に実装できますが、そこからの拡張がだいぶ難しく苦戦してます。 この日全体を通じて、質問することに対しての抵抗や無駄なプライドなども全部なくなり初心に帰ることができたと思うので来週以降もハングリー精神を忘れず、たくさん成長できるように頑張っていきたいです! まだまだ始まったばかり!強い仲間たちと一緒に頑張ります! では〜!