Skip to content

Latest commit

History

History
1043 lines (770 loc) · 46.9 KB

File metadata and controls

1043 lines (770 loc) · 46.9 KB

JavaScriptを始めよう

想定時間: 90分 動作環境: Google Chrome

TOC

1. JavaScript に触れてみよう

JavaScript(以下 JS )とは一般に Web 開発やシステム開発に用いられるプログラミング言語の一つです。
今回はインターンでのチーム開発にあたって基礎的な JS の構文や処理について説明します。

注意
本記事には一部の説明を簡単にするために、厳密には正しくない表現・説明があります。
なるべく正確な記述を行いますが、気になる箇所は参考文献にある記事などから調べて貰えればと思います。

1-1. ブラウザの開発者ツールを開いてみよう

Chrome ではウィンドウ右上のケバブボタンから開いたメニューの「その他のツール」から「デベロッパーツール」を開けます。

開発者ツールをひらく

ショートカットキーが設定されているため、Windows や ChromeOS では Ctrl+Shift+I で、macOS では Cmd+Option+I でも開くことができます。また、ページの適当な部分で右クリックして「検証」を押すことでも開発者ツールが開けます。
無事にツールが開けていれば以下のような表示になるはずです。

開発者ツールを開いた見た目

開いているタブが要素タブ( Element タブ)ではなかったり、日本語化の案内が出るかもしれませんが、ひとまずコンソールタブ( Console タブ)を開いてください。
これで準備完了です。

コンソールタブ

1-2. Hello, World! しよう

「新たにプログラミング言語の学習を始めるとき、まず Hello, World! から始める教」に入信しているため、まずは標準的な出力方法で「Hello, World!」を表示させることにします。
先程開いたコンソールタブに以下のコードを入力してみてください。

console.log('Hello, World!');

入力したら Enter キーで実行してみましょう。 実行すると以下の画像のように表示されるはずです。

Hello, World!

このように、JS では console.log(<任意の文字列>); とすることでコンソールタブに出力することができます。

2. 読みやすいコードを書くために

これから始まるインターンやその他でも「チーム開発」をしなければならないタイミングがあると思います。
そんなとき最も大事なことの一つが「コードを読んだ人間に処理の意味・意図が伝わること」です。
この章では自分以外の人が見ても読みやすいコードを書くために、JS の機能やプログラムを書く上で気をつける点を説明します。

2-1. 厳格モード

厳格モード( strict モード)とは、そうでない状態ではエラーにはならない微妙な実装をエラー扱いとするモードです。
これによって、暗黙的に失敗していた処理をエラーとして検知できるようになったり、JS のエンジンが最適化しづらい手順を修正できるようになったりといったメリットがあります。
しかし、利用したい構文が strict モードに非対応の場合もあるため注意が必要です。

厳格モードは JS のコード内に "use strict"; と記述することで有効化できます。 試しに以下のコードを実行してみてください。

"use strict";a=1;

未宣言の変数に値を代入しようとしたエラー

上の画像のようにエラーが出れば問題なく厳格モードが有効になっています。
この場合は宣言していない変数を操作しようとしたことでエラーが発生しています。これは意図せずグローバル変数を宣言してしまうのを防いでくれます。

このように便利な厳格モードですが、スクリプト全体で有効にしてしまうと他のライブラリやファイルが厳格モードに対応しない記述で書かれていたときには動作しなくなってしまうため注意が必要です。
おすすめの使用方法としては関数の冒頭部で有効化することです。そうすることで厳格モードは関数内でのみ有効になります。

2-2. コメント

コメントは他のほぼすべてのプログラミング言語にもある「コード中に記述されているが処理に影響がない文」のことです。
JS では主に以下の二通りで記述できます。

console.log('コメントの説明');// console.log('// は一行だけをコメントにする');console.log('//が書いてある行にしか影響しない');/** * console.log('/* と * / で囲むと複数行をコメントにする'); * console.log('そのため /* から * / までの処理は実行されない'); */

コメントを適切に書くことでコードの意図や内容を他の開発者にわかりやすく伝えることができます。
また、実行したくないコードをコメントにすることで開発中のデバッグをやりやすくする使い方もあります。

2-3. 命名

命名はプログラムを書く上で逃れられず、かつ難しい要素の一つです。「名は体を表す」ということわざがありますが、意識するべきはまさにこれです。
他人が書いたコード(もしかしたら過去の自分が書いたコード)をすべて読んで、その意味を理解するのは非常に時間がかかり、骨が折れる作業です。
そのため、簡潔で処理の内容や意味、効果がわかりやすい命名を変数・関数にするべきだとされています。

良い例悪い例
let count = 0;let a = 0;
function solveQuadraticEquation () {...}function solve() {...}

1つ目の例では count という名前で宣言されている方が変数の利用目的が明確です。
2つ目の例では場合によってどちらがいいかの判断が変わることがあります。solveQuadraticEquation という名前の方が処理の内容が明確です。しかし(特に)クラスのメンバとしてある式を解くような関数の場合は solve という名前も悪いとは言い切れません。 エディタの補完機能がない場合には冗長な命名は嫌われるため、むしろ solve の方が適切かもしれません。

時と場合に合わせて命名とコメントを組み合わせて誰が読んでもわかりやすいコードになるよう心がけましょう。

2-4. インデント

インデントとは、行頭の字下げのことを指します。主にコード中のブロックをわかりやすくするために利用されます。
Python のようなインデントベースの言語ではブロックそのものを表すために使われますが、JS ではコードのブロックは {} で表現されるため、インデントがなくてもエラーになったりはしません。 しかしインデントをつけることは処理の塊を理解しやすくするために非常に重要な作業です。意識してつけるようにしましょう。なお、{} で囲まれた文をそれまでの字下げよりもう1段階深くインデントするのが一般的です。

インデントなし

functionmyFunc(){leti=0;for(i=0;i<10;i++){console.log(i);}}

インデントあり

functionmyFunc(){leti=0;for(i=0;i<10;i++){console.log(i);}}

3. JavaScript で処理させよう

ここでは様々な計算や処理を JS にやってもらう方法を説明します。

3-1. 変数

変数は値を格納する入れ物です。例えるならラベルが付いた箱のようなものです。
変数を宣言するには let の後に変数の名前を記述します。

letmyVariable;

変数のあとに = と値を順番に記述すると、その値を変数に割り当てることができます。
値の割り当てと変数の宣言をまとめて、一行で記述することもできます。

letmyVariable;myVariable=1;letnextVariable=100;

3-2. 定数

定数は変数とは違い、一度割り当てると後から変更することはできません。
定数を宣言するには let のかわりに const を使います。

constmyConstantValue=10;// 一度割り当てると、後から変更することはできない// そのため以下のコードを実行するとエラーになる// myConstantValue = 100;

3-3. 算術演算

まず計算といえばな四則演算について説明します。それぞれ以下のようにして記述することができます。

演算記述実行結果
加法1 + 1加法演算
減法1 - 1減法演算
乗法2 * 2乗法演算
除法2 / 2除法演算
剰余3 % 2剰余演算

この他に +=-= のような二項演算子の後ろに = をつける、加算代入演算子や減算代入演算子などもあります。
この演算子は計算と変数への値の割り当てを一行で記述できます。

letvalue=1;value+=2;// value = value + 2; と同じ

他にも変数の値を一つ増やすインクリメントや、逆に減らすデクリメントもあります。
また、三角関数のような高度な演算は Math という組み込みオブジェクトから呼び出すことができます。
以下にいくつか例を示します。

演算記述実行結果補足
インクリメント後置インクリメント x++
前置インクリメント ++x
後置インクリメント処理
前置インクリメント処理
後置:先に評価結果を返して加算を行う
前置:先に加算を行って評価結果を返す
デクリメント後置デクリメント x--
前置デクリメント --x
後置デクリメント処理
前置デクリメント処理
後置:先に評価結果を返して減算を行う
前置:先に減算を行って評価結果を返す
絶対値Math.abs(1)
Math.abs(-1)
正の値の絶対値
負の値の絶対値
最小値Math.min(1, 2)最小値
最大値Math.max(1, 2)最大値
べき乗2 ** 2
Math.pow(2, 2)
べき乗演算子
Mathオブジェクトを利用
べき乗演算子 ** が後に実装された
円周率Math.PI円周率
正弦Math.sin(θ)正弦
余弦Math.cos(θ)余弦
正接Math.tan(θ)正接

その他の Math オブジェクトのプロパティやメソッドはこちらから確認できます。

3-4. 文字列

文字列は JS では String オブジェクトとして扱われ、様々な操作を行うことができます。
一部の操作方法を以下に示します。

操作記述実行結果
結合'jig' + '.' + 'jp'結合
str += otherStr結合
長さ'jig.jp'.length文字列の長さ
文字列の取り出し'jig.jp'.substring(0, 3)文字列の取り出し
文字の取り出し'jig.jp'.charAt(1)文字取り出し
分割'jig.jp'.split('.')分割
切り出し'jig.jp'.slice(1, 4)文字列の切り出し
置き換え'jig.jp'.replace('ji', 'じぇいあい')文字列の置き換え
位置'jig.jp'.indexOf('jp')文字列の位置

その他の String オブジェクトのプロパティやメソッドはこちらから確認できます。

3-5. 論理演算

論理演算はプログラムで頻出する演算の一つです。それぞれの演算は以下のように表せます。

演算記述実行結果
ANDa && band
ORa || bor
NOT!anot

4. 条件によって処理を変えよう

数学の問題で場合分けが発生するように、プログラムでも条件によって処理を変更したいことがあります。
ここでは二次方程式の判別式を例に、条件分岐を記述する方法を説明します。

4-1. 比較演算子

まずはじめに判別式 $D$ は以下の式で表され、 $D$ の値によって3つの分岐がわかります。

$D = b^2 - 4ac$

  • 正の値 のとき 実数解をもつ
  • 0 のとき 重解をもつ
  • 負の値 のとき 虚数解をもつ

ここで $D$ の結果を value という変数に置き換えたとき、それぞれの条件は比較演算子を用いて以下のように表せます。

  • 正の値 → value > 0
  • 0 → value === 0
  • 負の値 → value < 0

JS ではこのようにして値の大小や等価かどうかを比較できます。以下に比較演算子の表を示します。

演算記述
厳密等価a === b
厳密不等価a !== b
超過 / より大きいa > b
以上a >= b
未満 / より小さいa < b
以下a <= b
厳密でない等価 / 不等価

厳密等価 / 厳密不等価とは逆に、厳密でない等価 / 不等価も存在します。
これらは暗黙的な型変換を行うため、一見して予想できない挙動をすることがあります。
このため、厳密等価 / 厳密不等価演算子を使用することが推奨されます。
以下に厳密でない等価 / 不等価演算子の表を示します。

演算記述
等価==
不等価!=

4-2. if 文

4-1. では比較演算子を用いて条件を表現する方法を説明しました。ここでは判別式 $D$ の結果を評価し、実数解・重解・虚数解のどれをもつのか出力するプログラムを考えてみましょう。

評価の結果によって処理を分岐させるためには if 文を使います。

if(<条件1>) {条件1が真のときの処理} else if (<条件2>) {条件2が真のときの処理} else {条件1と条件2が偽のときの処理}

if 文は () 内の評価結果が真のとき、直後の {} で囲まれたブロック内の処理を行います。反対に偽であるときは直後の else 句の処理を行います。このとき、else のあとに if で if 文をつなげることで条件分岐を追加できます。
else 句のあとにある {} ブロック内の処理は、それより前にある ifelse if のすべての条件を満たさなかったときに実行されます。
if から else までは一連の処理の塊として捉えられます。この中で上から順に評価が行われるため、条件の順番には気をつける必要があります。

では判別式 $D$ の結果を評価して、どの解をもつのか出力するプログラムを以下に示します。

leta,b,c;a=<x2の係数>;
b = <xの係数>;
c = <定数項>;
const d = b ** 2 - 4 * a * c;
if (d === 0) {console.log('重解');} else if (d >0){console.log('実数解');} else {console.log('虚数解');}

実行結果は以下のようになります。

重解実数解

4-3. 三項演算子

三項演算子とは条件によって返す値を変えられる演算子です。 以下のような記法で利用できます。

value=<条件> ? <条件が真のときの値> : <条件が偽のときの値>;

例えばある値の真偽に応じて値を返したいときに活用できます。4-2. の判別式の例を書き換えると以下のようになります。

leta,b,c;a=<x2の係数>;
b = <xの係数>;
c = <定数項>;
const d = b ** 2 - 4 * a * c;
const res = d === 0
? '重解'
: d >0
? '実数解'
: '虚数解';console.log(res);

このように三項演算子の下に、新たに三項演算子を書くことができます。 しかし階層が深くなると複雑になり、読みづらくなるので注意が必要です。

三項演算子三項演算子

5. 繰り返し処理をさせよう

複数回処理をするとき、何度も同じコードを書くのは面倒です。ですよね?
ここではフィボナッチ数列を例に、同じ処理を複数回行うための便利な文法について説明します。

5-1. 配列

繰り返し処理と関連の深い値として配列があります。
配列とは変数が列になって連なったようなもので、以下のような記述で利用できます。

constarray=[1,2,3,4,5];// array の1番目の要素にアクセスするconsole.log(array[0]);// 1// 同じように array の4番目の要素にアクセスするconsole.log(array[3]);// 4// 存在しない要素にはアクセスできないconsole.log(array[5]);// undefined

配列のサンプル

配列は [] で囲まれ , で区切られた一連の値で表現されます。
個々の値にアクセスするには配列名の後ろに [index] と書きます。この index とは配列の中の順番のことで、0番から順に番号が振られます。
この番号は1からではなく、0から数えることに注意してください。ほとんどのプログラミング言語では0から数を数えるので覚えておきましょう。

5-2. for 文

先程の配列を使って、繰り返し処理の実例を見てみましょう。 繰り返し処理をするには for 文を使います。

for(<ループ前の処理>; <ループ終了条件>; <ループごとの処理>) {<繰り返す処理内容>}

<ループ前の処理> は、ループがはじまる前に一度だけ処理されます。
i, j, k を制御変数として利用し、let i = 0; のように変数宣言と初期値の代入をすることが多いです。

<ループ終了条件> は、評価結果が偽になったときにループを抜けるためのものです。
制御変数を含めた i < 5 のような判定式を記述することが多いです。

<ループごとの処理> は、<繰り返す処理内容> が完了した後に毎回処理されます。
i++ のように制御変数をインクリメントすることが多いです。

では具体的なフィボナッチ数列を生成するコードを見てみましょう。

letfibonacciSeries=[];for(leti=1;i<=10;i++){constlen=fibonacciSeries.length;if(len<2){fibonacciSeries.push(1);}else{fibonacciSeries.push(fibonacciSeries[len-2]+fibonacciSeries[len-1]);}}console.log(fibonacciSeries);// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

実行結果

このように面倒な手順を for 文を使うことで簡単に記述できました。

負の値をインデックスに使いたい

Python など一部言語では配列のインデックスに負の値を入力することで配列の後側から値を取り出すことができます。JS では通常の array[index] ではこの記法が利用できませんが、Array オブジェクトの at() メソッドを利用することで同様の処理が可能です。

letfibonacciSeries=[];for(leti=1;i<=10;i++){if(fibonacciSeries.length<2){fibonacciSeries.push(1);}else{fibonacciSeries.push(fibonacciSeries.at(-2)+fibonacciSeries.at(-1));}}console.log(fibonacciSeries);

個人的にはこの書き方のほうが行数も減って直感的にわかりやすくて好みです。適宜使い分けられると良いでしょう。

5-3. Array.prototype.forEach()

配列には .forEach() というメソッドがあります。メソッドとはオブジェクトから呼び出せる関数のことで、メソッドや関数については 6-1. で詳しく説明します。 .forEach() は配列の1要素ごとに繰り返し処理を行うためのもので、以下のようにして利用できます。

letarray=[];for(leti=0;i<10;i++){array.push(Math.floor(Math.random()*10*i));}array.forEach((value)=>{if(value%2===0){console.log(`${value} is even.`);}else{console.log(`${value} is odd.`);}});

このコードは10個の乱数を生成して配列にしたあと、それぞれに対して偶数か奇数かを判定するコードです。
(value) => {} と渡している処理をコールバック関数と呼びます。このように JS では関数の引数に関数を渡すことがあります。

同様の処理は for 文でも記述できますが、インデックスによるアクセスが発生し混乱のもとになりやすいです(特に配列のインデックスが0から始まることを忘れているとバグのもとになります)。

letarray=[];for(leti=0;i<10;i++){array.push(Math.floor(Math.random()*10*i));}for(leti=0;i<10;i++){if(array[i]%2===0){console.log(`${array[i]} is even.`);}else{console.log(`${array[i]} is odd.`);}}

foreachindex

for 文中の処理の最初で array[i] を別の変数(例えば value )に代入しても良いですが、同様のことが forEach() では (value) => {...} とするだけで書くことができます。また for 文での処理と違い配列の長さを気にせず処理を行えるのも便利な特徴です。適宜使い分けると良いでしょう。

6. コードをまとめてわかりやすくしよう

ここまでで JS での基本的な処理の説明を行ってきました。 プログラミングではそれらを組み合わせて多種多様な処理を作っていくわけです。
しかし処理のたびに毎回同じコードを書くのは気が引けますよね? もし一連の処理に名前をつけて呼び出せたら...。
ここではそれを実現する愉快な仲間たちを紹介します。

このセクションは全体的にだいぶ端折った説明をしています。
より詳細な説明は参考文献からmdn web docsJSPrimerの該当箇所を読んでください。

6-1. 関数

6-1-1. 関数

すでに関数の呼び出しは資料中にたくさん登場しています。例えば console.log() は立派な関数呼び出しです。
このように関数は 関数名(引数) という形で呼び出せます。

プログラム中では基本的に以下のように記述できます。

function<関数名> (<引数>) {<処理内容>
return <返り値>;
}

例えば二次方程式の解を求める関数を作成してみます(ただし、JS は標準では虚数を表現できません)。

functionsolveQuadraticEquation(a,b,c){constd=b**2-4*a*c;if(d===0){return{type: '重解',ans: [b/(2*a)]};}elseif(d>0){return{type: '実数解',ans: [(b+Math.sqrt(d))/(2*a),(b-Math.sqrt(d))/(2*a)]};}else{return{type: '虚数解',ans: [`(${b} ± √${Math.abs(d)}i) / ${2*a}`]};}}// 関数を呼び出すletresult=solveQuadraticEquation(3,5,7);

ここで a, b, c仮引数と呼ばれ、関数の呼び出し時に () の中の対応する位置に与えられた値(引数)を参照できます。
その後 return で関数の処理結果を返り値として呼び出し元に返しています。この return 文は値を返す必要がない関数では省略可能です。
return は値を返す、つまり関数の処理を終えたことを意味します。そのため上記の処理のように処理の途中で関数を終了したり、条件によって複数の終了処理を記述することができます。

6-1-2. 無名関数

JS で頻出する関数の書き方として、名前はないけど関数として宣言されて実行されるものがあります。それが無名関数です。

以下のようなものが無名関数と呼ばれます。

function(msg){console.log(msg);};(msg)=>{console.log(msg);};

後者は特別にアロー関数と呼ばれる場合もあります。これらは返り値として関数を返します。
そのため、以下のように変数に代入して変数名の後ろに () をつけることで代入した関数を呼び出すことができます。

constlog=function(msg){console.log(msg);};log('test');

6-1-3. 値としての関数

6-1-2. では無名関数を変数に代入していました。このことから、JS の関数は値として扱えることがわかります。
この性質を利用してコールバックという処理方法を取れます。setTimeout(callback, delay)callback のように関数を値として渡すことで特別な処理がしやすくなります。

setTimeout(()=>{constnow=newDate();console.log(now);},5*1000);

setTimeout

setTimeout

setTimeoutsetTimeout(callback, delay) のように2つの引数を取ります。
callback はコールバック関数で delay ミリ秒後に実行されます。
また、返り値として正の整数値を返します。これは登録されたタイマーを一意に識別するためのIDです。
setTimeout が呼ばれてから delay ミリ秒の間に clearTimeout(timeoutID) とすることで登録されたコールバック関数の実行をキャンセルできます。

6-2. オブジェクト

6-2-1. 定義とアクセス

オブジェクトの名はここまでにも登場していますが、あらためて説明します。
JS におけるオブジェクトとは、キーと値が対になったプロパティの集合です。
以下の文法で定義・アクセスできます。

letobj={key1: 'value1',key2: 'value2'};console.log(obj.key1,obj['key2']);

オブジェクトのサンプル実行結果

このとき、[](ブラケット記法)を利用したアクセスでは obj['key2'] のように、プロパティ名を文字列として記述するほうが望ましいです。
仮に obj[key2] と記述してアクセスしようとしたとき、key2 が変数として解釈されて未定義のためエラーが発生します。
これに対して、.(ドット記法)を利用したアクセスでは、使えないプロパティ名があることに注意が必要です。
数字で始まるプロパティ名やハイフンを含んだプロパティ名はブラケット記法でアクセスする必要があります。

6-2-2. プロパティの追加と存在確認

JS のオブジェクトは、一度作成したあとその値自体を変更できる特性を持ちます。これは const を利用して宣言したときも同様です。
そのため、以下のようにしてオブジェクトにプロパティを追加できます。

constobj={};obj.key1='value1';obj['key2']='value2';console.log(obj.key1,obj['key2']);

オブジェクトにプロパティを追加

またこの特性から、オブジェクトにないプロパティも参照できてしまいます(参照すると undefined が返ります)。
この挙動によるバグを回避するために、いくつかの方法でオブジェクトに目的のプロパティが存在するかを確認することができます。

ここでは最も使いやすい手法として Optional Chaining 演算子?. を用いた方法を以下に示します。

constobj={prop1: {key1: 'value1'},key2: 'value2'};console.log(obj.key1);// X undefinedconsole.log(obj.key2);// O 'value2'console.log(obj.prop1);// O {key1: 'value1'}console.log(obj.prop1.key1);// O 'value1'console.log(obj.prop1.key2);// X undefinedconsole.log(obj.prop2);// X undefinedconsole.log(obj.prop2.key1);// エラー undefined に対して更にプロパティにアクセスしようとしたconsole.log(obj.prop2.key2);// エラー undefined に対して更にプロパティにアクセスしようとした// optional chaining// `?.` のつなげられたプロパティが存在するかを確認して// 存在すれば `?.` でつながったプロパティにアクセスする// 存在しなければ undefined を返すconsole.log(obj.prop2?.key1);// X undefinedconsole.log(obj?.prop2.key1);// エラー ( obj?.prop2 が undefined になり、undefined.key1 と同じ意味になる)console.log(obj?.prop2?.key1);// X undefined ( obj?.prop2 が undefined になり、undefined?.key1 が undefined になる)

例えば、API リクエストのレスポンスにあったりなかったりするプロパティにアクセスするときや、入力が必須でない項目があるフォームなどを扱うときに重宝する機能です。
覚えていると良いことがあるかもしれません。

オブジェクトのプロパティに関数を

JS の関数は値として扱える、という話をしましたが、ならばキーと値が対になったプロパティに関数を使うこともできそうですよね?
できます。

constbasicArithmeticOperations={sum: (a,b)=>a+b,diff: (a,b)=>a-b,multi: (a,b)=>a*b,div: (a,b)=>a/b};console.log(basicArithmeticOperations.sum(1,1));console.log(basicArithmeticOperations.diff(1,1));console.log(basicArithmeticOperations.multi(2,2));console.log(basicArithmeticOperations.div(2,2));

関数をプロパティに

6-3. クラス

クラスは以下のように定義し、インスタンスを生成してメソッドやプロパティにアクセスできます。

// クラス定義class<クラス名>{<プロパティの宣言>
constructor (<コンストラクタ引数>) {<コンストラクタ関数での処理><コンストラクタ関数では `return` は基本的にしない>}<メソッド(関数)の定義>}// インスタンス生成<constまたはlet><インスタンス変数名> = new <クラス名>(<コンストラクタ引数>);
// メソッド・プロパティアクセス
<インスタンス変数名>.<プロパティまたはメソッド>

↑の疑似コードでは分かりづらい部分もあるので具体的に...

例えば

  • クラス名: MyClass
    • 文字列を与えて初期化できる(与えなくても初期値をもつ)
    • printText メソッドを呼び出すことで自身が持つ文字列を出力する

というクラスを実装してみます。

classMyClass{text='initial text';constructor(text){if(text)this.text=text;}printText(){console.log(this.text);}}

これがクラスです。クラスは設計書のようなもので、これをもとに実体(インスタンス)を生成します。

constmyClass=newMyClass('my text');

これで自身の文字列として 'my text' を持つ MyClass のインスタンスを生成して myClass に代入できました。 myClass から printText メソッドを呼び出せば 'my text' と出力されるはずです。

myClass.printText();

クラスのサンプルコード実行結果

7. 非同期処理を使おう

非同期処理とはその名の通りプログラム中で非同期的な処理を行うことです。例えばサーバーへのリクエストを送ってレスポンスを受けとりたいときに使用されます。
ここまで紹介してきたコードはすべて同期処理で、非同期処理とは対称的に書いてあるコードが上から順に処理されていきます。しかし、同期処理ではサーバーへのリクエストを送ったあとレスポンスが返ってくるまで何もすることができず(正確には「レスポンスを待つ」ことが処理になっているため次の処理に移れず)、処理にかかる時間が大幅に伸びてしまったりします。
この待ち時間を解消するための仕組みが非同期処理です。

---
title: 同期処理
---
sequenceDiagram
クライアント->>+ホスト: リクエスト
Note right of クライアント: 待ち時間
ホスト-->>-クライアント: レスポンス
Loading
---
title: 非同期処理
---
sequenceDiagram
クライアント->>+非同期処理: 処理登録
非同期処理->>+ホスト: リクエスト
Note right of クライアント: 他の処理
ホスト-->>-非同期処理: レスポンス
非同期処理-->>-クライアント: コールバック呼び出し
Loading
JS における非同期処理の捉え方

JS の処理は基本的にシングルスレッドで、実際には↑の図のような形にはなりづらいです(実現する方法はあります)。
非同期処理は並列に処理される(同時の2つの処理が行われる)わけではなく平行に処理されます(一つの処理の流れの中で2つの処理が行われる)。イメージとしては2人の人間が一つずつの作業を行うわけではなく、1人の人間が順序をつけて2つの作業を行う感じです。
JS における非同期処理は、サーバーからもらうデータが必要などの理由で、今すぐに実行することができない処理を一旦後回しにして、必要なデータが揃ってから処理していく という認識が妥当に思います。

詳しくはこちらのページがわかりやすいです。

7-1. Promise / then() / catch()

ここでは非同期処理の状態や結果を扱うことのできる Promise オブジェクトについて説明します。

constrandomDelay=()=>newPromise((resolve,reject)=>{constdelay=Math.floor(Math.random()*1000);setTimeout(()=>{if(delay%2===0){resolve(`${delay} is even.`);}else{reject(`${delay} is odd.`)}},delay);});randomDelay().then(v=>console.log(`resolve: ${v}`)).catch(e=>console.log(`reject: ${e}`));

↑のコードではランダムな0~1000ミリ秒後に、待機時間が偶数秒なら resolve で解決され、奇数秒なら reject で拒否される Promise インスタンスを返す関数 randomDelay を宣言しています。 その後、randomDelay() でインスタンスを生成し、Promisethen() / catch() で受け取って解決・拒否を処理しています。

Promiseサンプル実行結果

7-2. async / await

async は非同期関数を定義するための構文です。これは必ず Promise インスタンスを返す関数で通常の関数定義の構文とは異なります。 それに対して awaitPromise インスタンスを右辺にとり、その状態が解決もしくは拒否されるまでその場で待機するものです。

これらを用いて 7-1. の randomDelay 関数を実装すると次のようになります。

asyncfunctionrandomDelay(){conststartedAt=newDate();constdelay=Math.floor(Math.random()*1000);while(true){if(startedAt.getTime()+delay<(newDate().getTime()))break;}if(delay%2===0){return`${delay} is even.`;}else{returnPromise.reject(`${delay} is odd.`);}}try{constv=awaitrandomDelay();console.log(`resolve: ${v}`);}catch(e){console.log(`reject: ${e}`);}

ランダムな時間待機するコードが変わっていますが、処理内容は同様です。気になる方は以下のサマリーを確認してください。
ここで新たに try ~ catch という構文が登場しますが、これは例外処理のための文です。 Promise ~ then ~ catch では例外を catch がひろってくれるのですが、await 式を用いた実装では例外は明示的に処理しなければなりません。そのため、この記述が必要になります。

asyncサンプル実行結果

JS で処理を一時待機させる方法

↑のコードでは関数が呼ばれた時刻を startedAt で保持して、while 文を利用して現在時刻と比較し続けて delay 秒経過したら breakwhile ループを抜ける処理になっています。 この方法は処理の負荷が非常に高いため、普通まずやってはいけない処理です。

ではどのような方法で実装できるかというと、以下の定義で sleep 関数を用意することができます。

constsleep=delay=>newPromise(resolve=>setTimeout(resolve,delay));

これを使うことで以下のようにコードを書き直せます。

asyncfunctionrandomDelay(){constdelay=Math.floor(Math.random()*1000);awaitsleep(delay);if(delay%2===0){return`${delay} is even.`;}else{returnPromise.reject(`${delay} is odd.`);}}try{console.log(awaitrandomDelay());}catch(e){console.log(e);}

関数定義を一つだけにしたい、await の使用箇所を一つにしたいという理由で非推奨なコードをサンプルで使用しました。 特別な事情がないかぎり、sleep 関数を定義して利用するのが良いでしょう。


処理によって Promise ~ then を利用するか async / await を利用するか、どちらが読みやすいかを考えながら使い分けられると良いですね。

8. ブラウザの標準 API を使ってみよう

さて、最後に web 開発を助けてくれるブラウザの機能について説明します。
我々が普段使うブラウザには様々な API が用意されています。特に有用な2つを使ってみましょう。

8-1. Web Storage API

Web Storage API はブラウザにキーと値の対を保存できる API です。 その中でも特に localStorage について説明します。

localstorage は以下のようにして利用できます。

// 値のセットlocalStorage.setItem('name','jig太郎');// 値の取り出しconsole.log(localStorage.getItem('name'));// jig太郎// ローカルストレージに保存されている値の数を取得console.log(localStorage.length);// 1// 値の削除localStorage.removeItem('name');console.log(localStorage.getItem('name'));// nulllocalStorage.setItem('name','jig太郎');localStorage.setItem('age','20');// ローカルストレージを空にするlocalStorage.clear();console.log(localStorage.length);// 0

localStorageサンプル

注意点として、localStorage に保存する値はすべて文字列になることが挙げられます。
例えば、localStorage.setItem('age', 20); として number 型で保存できたと思っても、getItem で取り出したときには '20' として文字列で返ってきます。このあと数値として扱いたい場合には Number(<文字列>) として number 型に変換する必要があります。
また、localStorage への操作はパフォーマンスが悪いという問題もあります。多用するのは問題ですが、扱いやすさ故に許容されがちです。気をつけて利用するようにしましょう。

8-2. 位置情報 API

インターネットにアクセスできる環境下で便利なものの一つが位置情報でしょう。手元の小さな端末が自分の位置を正確に取得してくれるおかげで我々は迷子になることが減りました。

そんな位置情報を取得するための API がブラウザにも実装されています。以下のようにして自分の緯度経度を取得することができます。

// 位置情報が利用可能か確認するif('geolocation'innavigator){constgeolocation=navigator.geolocation;geolocation.getCurrentPosition((position)=>{console.log(`latitude: ${position.coords.latitude}, longitude: ${position.coords.longitude}`);});}else{console.error('this browser has not support geolocation.');}

↑のコードを実行すると↓のようなポップアップが表示されます。「サイトへのアクセス時のみ許可する」または「今回のみ許可」をクリックして位置情報を利用できるようにしてください。

位置情報利用許可

実行結果

↑のように緯度経度が出力されれば問題なく実行できています。

地図で調べて見ると若干のズレはあるもののおおよその位置はあっていました。

位置情報から地図

位置情報の応用はこちらの記事mdn web docsを確認してみてください。

9. 終わりに

ここまでで JS の初歩〜ブラウザの標準 API を利用するまでを駆け足で説明してきました。
省略した部分や実際の内容とは異なる解説をしている部分もあるため、理解しきれてない部分もあるかもしれません。
わからないところや詳しく知りたいところは筆者やメンターに聞いたり、参考文献から調べたりしてみてください。

10. 参考文献