JavaScript
JavaScriptの基本知識
動的型付け言語です。Webページの開発に多く用いられています。JavaScriptはプロトタイプベースであり、オブジェクト指向プログラミングや関数型プログラミングなど、幅広いスタイルをサポートしています。 Node.js や Apache CouchDB や Adobe Acrobat などでも使用されています。
コメントアウト
コメントアウトは以下のように記述します。
変数と宣言
変数には以下のようなものがあります
変数宣言 | 特徴 |
---|---|
const | 再代入できない変数の宣言 |
let | 再代入可能な変数の宣言 |
var | 再代入可能な変数の宣言(使用未推奨) |
const
constキーワードでは、再代入できない変数の宣言とその変数が参照する値(初期値)を定義できます。
let
letキーワードでは、値の再代入が可能な変数を宣言できます。 letの使い方はconstとほとんど同じです。
var
varキーワードでは、値の再代入が可能な変数を宣言できます。 varの使い方はletとほとんど同じです。 varはletとよく似ていますが、varキーワードには同じ名前の変数を再定義できてしまう問題があります。
varは同じ名前の変数を再定義できます。 これは意図せずに同じ変数名で定義してもエラーとならずに、値を上書きしてしまいます。
varには変数の巻き上げと呼ばれる意図しない挙動があり、letやconstではこの問題が解消されています。 「letはvarを改善したバージョン」ということだけ覚えておくとよいです。
値の表示と評価
HTMLでJavaScriptを実行
例としてJavaScriptファイルとしてindex.jsファイルをexample/index.jsというパスに作成します。 index.jsの中には次のようなコードを書いておきます。
```javascript:index.js 1;
次にHTMLファイルとしてindex.htmlファイルをexample/index.htmlというパスに作成します。 このHTMLファイルから先ほど作成したindex.jsファイルを読み込み実行します。
```html:index.html
<html>
<head>
<meta charset="UTF-8">
<title>Example</title>
<script src="./index.js"></script>
</head>
<body></body>
</html>
"Web コンソール"を開いてみると、コンソールには何も表示されていないはずです。 REPLでは自動で評価結果のコンソール表示まで行いますが、JavaScriptコードとして読み込んだ場合は勝手に評価結果を表示することはありません。
Console API
JavaScriptの多くの実行環境では、Console APIを使ってコンソールに表示します。 console.log(引数)の引数にコンソール表示したい値を渡すことで、評価結果がコンソールに表示されます。
// 式の評価結果の例(コンソールには表示されない)
1; // => 1
const total = 42 + 42;
// 変数の評価結果の例(コンソールには表示されない)
total; // => 84
// Console APIでコンソールに表示する例
console.log("JavaScript"); // => "JavaScript"
実行結果
データの型
JavaScriptは動的型付け言語に分類される言語であるため、静的型付け言語のような変数の型はありません。 しかし、文字列、数値、真偽値といった値の型は存在します。 これらの値の型のことをデータ型と呼びます。 データ型を大きく分けると、プリミティブ型とオブジェクトの2つに分類されます。
プリミティブ型
プリミティブ型(基本型)は、真偽値や数値などの基本的な値の型のことです。 プリミティブ型の値は、一度作成したらその値自体を変更できないというイミュータブル(immutable)の特性を持ちます。 JavaScriptでは、文字列も一度作成したら変更できないイミュータブルの特性を持ち、プリミティブ型の一種として扱われます。
基本形 | データの型 |
---|---|
真偽値 | true または false |
値型 | 実数(-3, 56 etc)や浮動小数(3.456など) |
巨大数型 | 9007199254740992nなどの任意精度の整数 |
文字列 | "JavaScript"などのstring型 |
undefined | 値が未定義のデータ型 |
null | 値が存在しないデータ型 |
オブジェクト
プリミティブ型ではないものをオブジェクト(複合型)と呼び、 オブジェクトは複数のプリミティブ型の値またはオブジェクトからなる集合です。 オブジェクトは、一度作成した後もその値自体を変更できるためミュータブル(mutable)の特性を持ちます。 オブジェクトは、値そのものではなく値への参照を経由して操作されるため、参照型のデータとも言います。
配列、関数、正規表現などがこれにあたります
シングルクォートとダブルクォート
javascriptにおいては""も''も全く同じ意味となります。RubyやPHPでは異なります。
"や'を出力中に含めるのは\を直前につけます。
実行結果
変数の文字列中における出力
出力文字列中に変数を含ませたい場合は''で囲う必要があります
実行結果
オブジェクトの出力
JavaScriptにおいて、オブジェクトはあらゆるものの基礎となります。 そのオブジェクトを作成する方法として、オブジェクトリテラルがあります。 オブジェクトリテラルは{}(中カッコ)を書くことで、新しいオブジェクトを作成できます。
const obj = {
//Keyというプロパティの宣言
"key": "value"
};
// ドット記法
console.log(obj.key); // => "value"
// ブラケット記法
console.log(obj["key"]); // => "value"
オブジェクトはとても重要で、これから紹介する配列や正規表現もこのオブジェクトが元となっています。
ドット記法の注意点
ドット記法では、プロパティ名が変数名と同じく識別子である必要があります。 そのため、次のように識別子として利用できないプロパティ名はドット記法として書くことができません。
// プロパティ名は文字列の"123"
var object = {
"123": "value"
};
// OK: ブラケット記法では、文字列として書くことができる
console.log(object["123"]); // => "value"
// NG: ドット記法では、数値からはじまる識別子は利用できない
object.123
配列の出力
オブジェクトリテラルと並んで、よく使われるリテラルとして配列リテラルがあります。 配列リテラルは[と]で値をカンマ区切りで囲み、その値を持つArrayオブジェクトを作成します。
const array = ["index:0", "index:1", "index:2"];
// 0番目の要素を参照
console.log(array[0]); // => "index:0"
// 1番目の要素を参照
console.log(array[1]); // => "index:1"
正規表現による出力
JavaScriptは正規表現をリテラルで書くことができます。 正規表現リテラルは/(スラッシュ)と/(スラッシュ)で正規表現のパターン文字列を囲みます。
次のコードでは、数字にマッチする特殊文字である\dを使い、1文字以上の数字にマッチする正規表現をリテラルで表現しています。
const numberRegExp = /\d+/; // 1文字以上の数字にマッチする正規表現
// `numberRegExp`の正規表現が文字列"123"にマッチするかをテストする
console.log(numberRegExp.test("123")); // => true
演算子
演算子はよく利用する演算処理を記号などで表現したものです。 たとえば、足し算をする + も演算子の一種です。これ以外にも演算子には多くの種類があります。 演算子は演算する対象を持ちます。この演算子の対象のことを被演算子(オペランド)と呼びます。
二項演算子
演算子 | 演算した値の結果 |
---|---|
+ | 2つの数字の和もしくは文字列の結合 |
- | 2つの数字の差 |
* | 2つの数字の積 |
/ | 2つの数字の商 |
% | 2つの数字の商をとった余り |
** | べき乗の結果 |
単項演算子
演算子 | 演算した値の結果 |
---|---|
+(単項プラス演算子) | 文字列の数字も数値に変換し演算、数字でない場合も特殊値に変換 |
-(単項マイナス演算子) | 単項プラス演算子のマイナス版 |
++(インクリメント演算子) | オペランドの値を+1する演算子、前置か後置で評価の順番は変わるが最終的な演算結果は同じ |
--(デクリメント演算子) | インクリメント演算子のマイナス版 |
比較演算子
演算子 | 演算した値の結果 |
---|---|
=== | 2つのオペランドを比較し同じ型で同じ値の場合にTrue、そうでない場合Falseを返す |
!== | つのオペランドを比較し異なる型または異なる値の場合にTrue、そうでない場合Falseを返す |
== | ===の場合と基本は同じであるが、比較対象が異なる場合に暗黙的な型変換が行われ比較される(使用不推奨) |
!= | !==の場合と基本は同じであるが、比較対象が異なる場合に暗黙的な型変換が行われ比較される(使用不推奨) |
> | 左オペランドが右オペランドより大きいならば、trueを返します |
>= | 左オペランドが右オペランドより大きいまたは等しいならば、trueを返します |
< | 右オペランドが左オペランドより大きいならば、trueを返します |
<= | 右オペランドが左オペランドより大きいまたは等しいならば、trueを返します |
ビット演算子
表記割愛、必要が生じれば追記
論理演算子
基本的に真偽値を扱う演算子でAND(かつ)、OR(または)、NOT(否定)を表現できます。
演算子 | 演算した値の結果 |
---|---|
&& | AND |
|| | OR |
! | NOT |
条件(三項)演算子(?と:)
条件演算子は条件式を評価した結果がtrueならば、Trueのとき処理する式の評価結果を返します。 条件式がfalseである場合は、Falseのとき処理する式の評価結果を返します。
実行例
function addPrefix(text, prefix) {
// `prefix`が指定されていない場合は"デフォルト:"を付ける
const pre = typeof prefix === "string" ? prefix : "デフォルト:";
return pre + text;
}
console.log(addPrefix("文字列")); // => "デフォルト:文字列"
console.log(addPrefix("文字列", "カスタム:")); // => "カスタム:文字列"
型変換
暗黙的な型変換
処理において、その処理過程で行われる明示的ではない型変換のこと 意図しない型変換によるプログラムの予期しない動作やバグの原因となりえる。
明示的な型変換
任意値=>真偽値
真偽値の変換例
Boolean("string"); // => true
Boolean(1); // => true
Boolean({}); // => true
Boolean(0); // => false
Boolean(""); // => false
Boolean(null); // => false
falsy(下記値)な値以外はTrueになります。
- false
- undefined
- null
- 0
- 0n
- NaN
- ""
数値=>文字列
Stringコンストラクタを用います。
シンボル=>文字列
文字列から数値へ明示的に変換するにはNumberコンストラクタ関数が利用できます。
// 文字列として受け取る
const input = "45";
// 文字列を数値に変換する
const num = Number(input);
console.log(typeof num); // => "number"
console.log(num); // 入力された文字列を数値に変換したもの
明示的な型変換でない判定
空文字列かの判定
空文字列とは「String型で文字長が0の値」であると定義することで、isEmptyString関数をもっと正確に書くことができます。
// 空文字列かどうかを判定する関数
function isEmptyString(str) {
// String型でlengthが0の値の場合はtrueを返す
return typeof str === "string" && str.length === 0;
}
console.log(isEmptyString("")); // => true
// falsyな値でも正しく判定できる
console.log(isEmptyString(0)); // => false
console.log(isEmptyString()); // => false
関数と宣言
関数宣言
JavaScriptでは、関数を定義するためにfunctionキーワードを使います。 functionからはじまる文は関数宣言と呼び、次のように関数を定義できます
// 関数宣言
function 関数名(仮引数1, 仮引数2) {
// 関数が呼び出されたときの処理
// ...
return 関数の返り値;
}
// 関数呼び出し
const 関数の結果 = 関数名(引数1, 引数2);
console.log(関数の結果); // => 関数の返り値
デフォルト引数
デフォルト引数(デフォルトパラメータ)は、仮引数に対応する引数が渡されていない場合に、デフォルトで代入される値を指定できます。
呼び出しき引数が多い際の処理
関数の仮引数に対して引数の個数が多い場合、あふれた引数は単純に無視されます。
アロー関数
関数式にはfunctionキーワードを使った方法以外に、Arrow Functionと呼ばれる書き方があります。 名前のとおり矢印のような=>(イコールと大なり記号)を使い、匿名関数を定義する構文です。 次のように、functionキーワードを使った関数式とよく似た書き方をします。
アロー関数の特徴は以下のものです
- 名前をつけることができない(常に匿名関数)
- thisが静的に決定できる
- functionキーワードに比べて短く書くことができる
- newできない(コンストラクタ関数ではない)
- arguments変数を参照できない
たとえばfunctionキーワードの関数式では、値を返すコールバック関数を次のように書きます。 配列のmapメソッドは、配列の要素を順番にコールバック関数へ渡し、そのコールバック関数が返した値を新しい配列にして返します。
const array = [1, 2, 3];
// 1,2,3と順番に値が渡されコールバック関数(匿名関数)が処理する
const doubleArray = array.map(function(value) {
return value * 2; // 返した値をまとめた配列ができる
});
console.log(doubleArray); // => [2, 4, 6]
上記結果は下記コード結果の処理と同じです
const array = [1, 2, 3];
// 仮引数が1つなので`()`を省略できる
// 関数の処理が1つの式なので`return`文を省略できる
const doubleArray = array.map(value => value * 2);
console.log(doubleArray); // => [2, 4, 6]
コールバック関数
関数はファーストクラスであるため、その場で作った匿名関数を関数の引数(値)として渡すことができます。 引数として渡される関数のことをコールバック関数と呼びます。 一方、コールバック関数を引数として使う関数やメソッドのことを高階関数と呼びます。
たとえば、配列のforEachメソッドはコールバック関数を引数として受け取る高階関数です。 forEachメソッドは、配列の各要素に対してコールバック関数を一度ずつ呼び出します。
const array = [1, 2, 3];
array.forEach((value) => {
console.log(value);
});
// 次のように実行しているのと同じ
// output(1); => 1
// output(2); => 2
// output(3); => 3
高階関数を用いた処理
高階関数(map,filter,reduce,etc...)を用いて繰り返し処理を行うと、for文よりもコードの可視性が向上します。 また記述量も減らすことが可能です。
mapを用いたforの処理
まずfor文での配列を出力するプログラムは下記の通りです。
これをmapメソッドで表記すると以下の通りです。
map関数は、要素に対して行う処理を書いた関数を引数に与えてあげることで、for文同様配列内の各要素に対して同じ処理を繰り返し実行することができます。
#### mapを用いた新しい配列の生成 まずfor文での新しい配列を出力するプログラムは下記の通りです。
const x = [1,2,3];
let twice = []; // 事前に結果を受け取る変数を定義しておく
for (let i= 0; i<x.length; i++) {
twice.push(x[i] * 2);
}
console.log(twice); // [2, 4, 6]
これをmapメソッドで表記すると以下の通りです。
最終的に値を返却するためにreturnを付けているのに注意してください。
#### filterを用いたフィルタリング 各要素に対して条件判定を行い、その判定結果がtrueであったものを返す関数です
単純な評価であればif文すら不要になるためこちらも重宝します。
メソッド
オブジェクトのプロパティである関数をメソッドと呼びます。 JavaScriptにおいて、関数とメソッドの機能的な違いはありません。 しかし、呼び方を区別したほうがわかりやすいため、ここではオブジェクトのプロパティである関数をメソッドと呼びます。
次のコードではobj.method1プロパティとobj.method2プロパティがメソッドです。
const obj = {
method1: function() { ///: functionは省略可能
return "this is method"; // `function`キーワードでのメソッド
},
method2: () => {
// Arrow Functionでのメソッド
}
};
console.log(obj.method1()); // => "this is method"
条件分岐
if文
swicth文
switch (式) {
case ラベル1:
// `式`の評価結果が`ラベル1`と一致する場合に実行する文
break;
case ラベル2:
// `式`の評価結果が`ラベル2`と一致する場合に実行する文
break;
default:
// どのcaseにも該当しない場合の処理
break;
}
ループと反復処理
while文
while文は条件式がtrueであるならば、反復処理を行います。
反復処理を扱う際に、コードの書き間違いや条件式のミスなどから無限ループを引き起こしてしまう場合があります。
do-while文
do-while文はwhile文とほとんど同じですが実行順序が異なります。
実行フローは以下のようになります。
- 実行する文を実行
- 条件式 の評価結果がtrueなら次のステップへ、falseなら終了
- 一番上に戻る
for文
forEachメソッド
配列にはforEachメソッドというfor文と同じように反復処理を行うメソッドがあります。 forEachメソッドでの反復処理は、次のように書けます。
const array = [1, 2, 3];
array.forEach(currentValue => {
console.log(currentValue);
});
// 1
// 2
// 3
// と順番に出力される
break文
someメソッド
someメソッドは、配列の各要素をテストする処理をコールバック関数として受け取ります。 コールバック関数が、一度でもtrueを返した時点で反復処理を終了し、someメソッドはtrueを返します。
function isEven(num) {
return num % 2 === 0;
}
const numbers = [1, 5, 10, 15, 20];
console.log(numbers.some(isEven)); // => true
continue文
continue文は現在の反復処理を終了して、次の反復処理を行います。 continue文は、while、do-while、forの中で使うことができます。
サンプルコード、偶数を返す関数
// `number`が偶数ならtrueを返す
function isEven(num) {
return num % 2 === 0;
}
// `numbers`に含まれている偶数だけを取り出す
function filterEven(numbers) {
const results = [];
for (let i = 0; i < numbers.length; i++) {
const num = numbers[i];
// 偶数ではないなら、次のループへ
if (!isEven(num)) {
continue;
}
// 偶数を`results`に追加
results.push(num);
}
return results;
}
const array = [1, 5, 10, 15, 20];
console.log(filterEven(array)); // => [10, 20]
filterメソッド
配列から特定の値だけを集めた新しい配列を作るにはfilterメソッドを利用できます。 filterメソッドには、配列の各要素をテストする処理をコールバック関数として渡します。 コールバック関数がtrueを返した要素のみを集めた新しい配列を返します。
function isEven(num) {
return num % 2 === 0;
}
const array = [1, 5, 10, 15, 20];
console.log(array.filter(isEven)); // => [10, 20]
for-in文
for-in文はオブジェクトのプロパティに対して、反復処理を行います。
for-of文
次のようにfor...of文で、配列から値を取り出して反復処理を行えます。 for...in文とは異なり、インデックス値ではなく配列の値を列挙します。
オブジェクト
オブジェクトはプロパティの集合です。プロパティとは名前(キー)と値(バリュー)が対になったものです。 プロパティのキーには文字列またはSymbolが利用でき、値には任意のデータを指定できます。 また、1つのオブジェクトは複数のプロパティを持てるため、1つのオブジェクトで多種多様な値を表現できます。 配列や関数などもオブジェクトの一種です。
オブジェクトの生成
オブジェクトを作成するには、オブジェクトリテラル({})を利用します。
// プロパティを持たない空のオブジェクトを作成
// = `Object`からインスタンスオブジェクトを作成
const obj = new Object();
console.log(obj); // => {}
プロパティへのアクセス
オブジェクトのプロパティにアクセスする方法として、ドット記法(.)を使う方法とブラケット記法([])があります。
const obj = {
key: "value"
};
// ドット記法で参照
console.log(obj.key); // => "value"
// ブラケット記法で参照
console.log(obj["key"]); // => "value"
オブジェクトと分割代入
同じオブジェクトのプロパティを何度もアクセスする場合に、何度もオブジェクト.プロパティ名と書くと冗長となりやすいです。 そのため、短い名前で利用できるように、そのプロパティを変数として定義し直すことがあります。
const languages = {
ja: "日本語",
en: "英語"
};
const ja = languages.ja;
const en = languages.en;
console.log(ja); // => "日本語"
console.log(en); // => "英語"
このようなオブジェクトのプロパティを変数として定義し直すときには、分割代入(Destructuring assignment)が利用できます。
const languages = {
ja: "日本語",
en: "英語"
};
const { ja, en } = languages;
console.log(ja); // => "日本語"
console.log(en); // => "英語"
プロパティの追加
// 空のオブジェクト
const obj = {};
// `key`プロパティを追加して値を代入
obj.key = "value";
console.log(obj.key); // => "value"
プロパティの削除
オブジェクトのプロパティを削除するにはdelete演算子を利用します。
const obj = {
key1: "value1",
key2: "value2"
};
// key1プロパティを削除
delete obj.key1;
// key1プロパティが削除されている
console.log(obj); // => { "key2": "value2" }
プロパティの存在確認
in演算子を用いた確認
in演算子は、指定したオブジェクト上に指定したプロパティがあるかを判定できます。
const obj = { key: undefined };
// `key`プロパティを持っているならtrue
if ("key" in obj) { //true or false
console.log("`key`プロパティは存在する");
}
hasOwnPropertyメソッド
オブジェクトのhasOwnPropertyメソッドは、オブジェクト自身が指定したプロパティを持っているかを判定できます。 このhasOwnPropertyメソッドの引数には、存在を判定したいプロパティ名を渡します。
const obj = { key: "value" };
// `obj`が`key`プロパティを持っているならtrue
if (obj.hasOwnProperty("key")) {
console.log("`object`は`key`プロパティを持っている");
}
Optional chaining演算子
Optional chaining演算子(?.)は左辺のオペランドがnullish(nullまたはundefined)の場合は、それ以上評価せずにundefinedを返します。一方で、プロパティが存在する場合は、そのプロパティの評価結果を返します。 つまり、Optional chaining演算子(?.)では、存在しないプロパティへアクセスした場合でも例外ではなく、undefinedという値を返します。
const obj = {
a: {
b: "objのaプロパティのbプロパティ"
}
};
// obj.a.b は存在するので、その評価結果を返す
console.log(obj?.a?.b); // => "objのaプロパティのbプロパティ"
// 存在しないプロパティのネストも`undefined`を返す
// ドット記法の場合は例外が発生してしまう
console.log(obj?.notFound?.notFound); // => undefined
// undefinedやnullはnullishなので、`undefined`を返す
console.log(undefined?.notFound?.notFound); // => undefined
console.log(null?.notFound?.notFound); // => undefined
toStringメソッド
独自のtoStringメソッドを定義したオブジェクトをStringコンストラクタ関数で文字列化してみます。 すると、再定義したtoStringメソッドの返り値が、Stringコンストラクタ関数の返り値になることがわかります。
// 独自のtoStringメソッドを定義
const customObject = {
toString() {
return "custom value";
}
};
console.log(String(customObject)); // => "custom value"
オブジェクトの静的メソッド
静的メソッドはObjectそのものから呼び出せるメソッドです。
オブジェクトの列挙
オブジェクトはプロパティの集合です。 そのオブジェクトのプロパティを列挙する方法として、次の3つの静的メソッドがあります。
メソッド | 動作 |
---|---|
Object.keys | オブジェクトのプロパティ名を配列で返す |
Object.values | オブジェクトの値を配列にして返す |
Object.entries | オブジェクトのプロパティ名と値の配列の配列を返す |
const obj = {
"one": 1,
"two": 2,
"three": 3
};
// `Object.keys`はキーを列挙した配列を返す
console.log(Object.keys(obj)); // => ["one", "two", "three"]
// `Object.values`は値を列挙した配列を返す
console.log(Object.values(obj)); // => [1, 2, 3]
// `Object.entries`は[キー, 値]の配列を返す
console.log(Object.entries(obj)); // => [["one", 1], ["two", 2], ["three", 3]]
オブジェクトのマージと複製
Object.assignメソッドは、あるオブジェクトを別のオブジェクトに代入(assign)できます。 このメソッドを使うことで、オブジェクトの複製やオブジェクト同士のマージができます。 Object.assignメソッドの返り値は、targetオブジェクトになります。
オブジェクトのマージ
実行例
const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = Object.assign({}, objectA, objectB);
console.log(merged); // => { a: "a", b: "b" }
Spread構文によるマージの実行例
const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = {
...objectA,
...objectB
};
console.log(merged); // => { a: "a", b: "b" }
オブジェクトの複製
JavaScriptには、オブジェクトを複製する関数は用意されていません。 しかし、新しく空のオブジェクトを作成し、そこへ既存のオブジェクトのプロパティをコピーすれば、それはオブジェクトの複製をしていると言えます。
// 引数の`obj`を浅く複製したオブジェクトを返す
const shallowClone = (obj) => {
return Object.assign({}, obj);
};
const obj = { a: "a" };
const cloneObj = shallowClone(obj);
console.log(cloneObj); // => { a: "a" }
// オブジェクトを複製しているので、異なるオブジェクトとなる
console.log(obj === cloneObj); // => false
配列
配列はJavaScriptの中でもよく使われるオブジェクトです。 配列に格納したそれぞれの値のことを要素、それぞれの要素の位置のことをインデックス(index) と呼びます。 JavaScriptにおける配列は可変長です。 そのため配列を作成後に配列へ要素を追加したり、配列から要素を削除できます。
配列の作成とアクセス
const emptyArray = [];
const numbers = [1, 2, 3];
// 2次元配列(配列の配列)
const matrix = [
["a", "b"],
["c", "d"]
];
const array = ["one", "two", "three"];
console.log(array[0]); // => "one"
console.log(matrix[0][0]); // => "a"
配列のlengthプロパティは配列の要素の数を返します。 そのため、配列の最後の要素へアクセスするには array.length - 1 をインデックスとして利用できます。
const array = ["one", "two", "three"];
console.log(array.length); // => 3
// 配列の要素数 - 1 が 最後の要素のインデックスとなる
console.log(array[array.length - 1]); // => "three"
配列リテラルでは値を省略することで、未定義の要素を含めることができます。 このような、配列の中に隙間があるものを疎な配列と呼びます。
// インデックスが1の値を省略しているので、カンマが2つ続いていることに注意
const sparseArray = [ 1, , 3];
console.log(sparseArray.length); // => 3
// 1番目の要素は存在しないため undefined が返る
console.log(sparseArray[1]); // => undefined
オブジェクトか配列の判定
オブジェクトが配列かどうかを判定するにはArray.isArrayメソッドを利用します。 Array.isArrayメソッドは引数が配列ならばtrueを返します。
const obj = {};
const array = [];
console.log(Array.isArray(obj)); // => false
console.log(Array.isArray(array)); // => true
typeof演算子では配列かどうかを判定することはできません。 配列もオブジェクトの一種であるため、typeof演算子の結果が"object"となるためです。
TypedArray
JavaScriptの配列は可変長のみですが、TypedArrayという固定長でかつ型つきの配列を扱う別のオブジェクトが存在します。 TypedArrayはバイナリデータのバッファを示すために使われるデータ型で、WebGLやバイナリを扱う場面で利用されます。
TypedArrayはArray.isArrayのメソッドの結果がfalseとなることからも別物と考えてよいでしょう。
// TypedArrayを作成
const typedArray = new Int8Array(8);
console.log(Array.isArray(typedArray)); // => false
配列と分割代入
配列の指定したインデックスの値を変数として定義し直す場合には、分割代入(Destructuring assignment)が利用できます。 右辺の配列から対応するインデックスの要素が、左辺で定義した変数に代入されます。
const array = ["one", "two", "three"];
const [first, second, third] = array;
console.log(first); // => "one"
console.log(second); // => "two"
console.log(third); // => "three"
配列から要素の検索
その要素のインデックスが欲しい場合、その要素自体が欲しい場合、その要素が含まれているかという真偽値が欲しい場合に配列から指定した要素を検索します。
インデックスを取得
指定した要素が配列のどの位置にあるかを知りたい場合、Array#indexOfメソッドやArray#findIndexメソッドを利用します。
const array = ["Java", "JavaScript", "Ruby"];
const indexOfJS = array.indexOf("JavaScript");
console.log(indexOfJS); // => 1
console.log(array[indexOfJS]); // => "JavaScript"
// "JS" という要素はないため `-1` が返される
console.log(array.indexOf("JS")); // => -1
異なるオブジェクトだが値は同じものを見つけたい場合には、Array#findIndexメソッドが利用できます。 findIndexメソッドの引数には配列の各要素をテストする関数をコールバック関数として渡します。
// colorプロパティを持つオブジェクトの配列
const colors = [
{ "color": "red" },
{ "color": "green" },
{ "color": "blue" }
];
// `color`プロパティが"blue"のオブジェクトのインデックスを取得
const indexOfBlue = colors.findIndex((obj) => {
return obj.color === "blue";
});
console.log(indexOfBlue); // => 2
console.log(colors[indexOfBlue]); // => { "color": "blue" }
条件に一致する要素を取得
要素自体が欲しいということを表現するには、Array#findメソッドが使えます。 findメソッドには、findIndexメソッドと同様にテストする関数をコールバック関数として渡します。
// colorプロパティを持つオブジェクトの配列
const colors = [
{ "color": "red" },
{ "color": "green" },
{ "color": "blue" }
];
// `color`プロパティが"blue"のオブジェクトを取得
const blueColor = colors.find((obj) => {
return obj.color === "blue";
});
console.log(blueColor); // => { "color": "blue" }
// 該当する要素がない場合は`undefined`を返す
const whiteColor = colors.find((obj) => {
return obj.color === "white";
});
console.log(whiteColor); // => undefined
指定範囲の要素の取得
配列から指定範囲の要素を取り出す方法としてArray#sliceメソッドが利用できます。 sliceメソッドは第一引数に開始位置、第二引数に終了位置を指定することで、その範囲を取り出した新しい配列を返します。
const array = ["A", "B", "C", "D", "E"];
// インデックス1から4の範囲を取り出す
console.log(array.slice(1, 4)); // => ["B", "C", "D"]
// 第二引数を省略した場合は、第一引数から末尾の要素までを取り出す
console.log(array.slice(1)); // => ["B", "C", "D", "E"]
// マイナスを指定すると後ろからの数えた位置となる
console.log(array.slice(-1)); // => ["E"]
// 第一引数と第二引数が同じ場合は、空の配列を返す
console.log(array.slice(1, 1)); // => []
// 第一引数 > 第二引数の場合、常に空配列を返す
console.log(array.slice(4, 1)); // => []
真偽値の取得
指定した要素が含まれているかだけを知りたい場合に、 Array#findIndexメソッドやArray#findメソッドは過剰な機能を持っています。 次のコードは、Array#indexOfメソッドを利用し、該当する要素が含まれているかを判定しています。
const array = ["Java", "JavaScript", "Ruby"];
// `indexOf`メソッドは含まれていないときのみ`-1`を返すことを利用
const indexOfJS = array.indexOf("JavaScript");
if (indexOfJS !== -1) {
console.log("配列にJavaScriptが含まれている");
// ... いろいろな処理 ...
// `indexOfJS`は、含まれているのかの判定以外には利用してない
}
上記コードは隅々まで読まないといけないため、意図が明確ではなくコードの読みづらさにつながります。 そこで、Array#includesメソッドを利用します。 Array#includesメソッドは配列に指定要素が含まれているかを判定します。
const array = ["Java", "JavaScript", "Ruby"];
// `includes`は含まれているなら`true`を返す
if (array.includes("JavaScript")) {
console.log("配列にJavaScriptが含まれている");
}
includesメソッドは異なるオブジェクトだが値が同じものを見つけたい場合には利用できません。 真偽値を得るにはArray#someメソッドを利用できます。 Array#someメソッドはテストするコールバック関数にマッチする要素があるならtrueを返し、存在しない場合はfalseを返します
// colorプロパティを持つオブジェクトの配列
const colors = [
{ "color": "red" },
{ "color": "green" },
{ "color": "blue" }
];
// `color`プロパティが"blue"のオブジェクトがあるかどうか
const isIncludedBlueColor = colors.some((obj) => {
return obj.color === "blue";
});
console.log(isIncludedBlueColor); // => true
追加と削除
配列は可変長であるため、作成後の配列に対して要素を追加、削除できます。 要素を配列の末尾へ追加するにはArray#pushが利用できます。 一方、末尾から要素を削除するにはArray#popが利用できます。
const array = ["A", "B", "C"];
array.push("D"); // "D"を末尾に追加
console.log(array); // => ["A", "B", "C", "D"]
const poppedItem = array.pop(); // 最末尾の要素を削除し、その要素を返す
console.log(poppedItem); // => "D"
console.log(array); // => ["A", "B", "C"]
要素を配列の先頭へ追加するにはArray#unshiftが利用できます。 一方、配列の先頭から要素を削除するにはArray#shiftが利用できます。
const array = ["A", "B", "C"];
array.unshift("S"); // "S"を先頭に追加
console.log(array); // => ["S", "A", "B", "C"]
const shiftedItem = array.shift(); // 先頭の要素を削除
console.log(shiftedItem); // => "S"
console.log(array); // => ["A", "B", "C"]
配列同士の結合
Array#concatメソッドを使うことで配列と配列を結合した新しい配列を作成できます。
const array = ["A", "B", "C"];
const newArray = array.concat(["D", "E"]);
console.log(newArray); // => ["A", "B", "C", "D", "E"]
また、concatメソッドは配列だけではなく任意の値を要素として結合できます。
const array = ["A", "B", "C"];
const newArray = array.concat("新しい要素");
console.log(newArray); // => ["A", "B", "C", "新しい要素"]
配列の展開
Spread構文を使うことで、配列リテラル中に既存の配列を展開できます。 次のコードでは、配列リテラルの末尾に配列を展開しています。
const array = ["A", "B", "C"];
// Spread構文を使った場合
const newArray = ["X", "Y", "Z", ...array];
// concatメソッドの場合
const newArrayConcat = ["X", "Y", "Z"].concat(array);
console.log(newArray); // => ["X", "Y", "Z", "A", "B", "C"]
console.log(newArrayConcat); // => ["X", "Y", "Z", "A", "B", "C"]
配列をフラット化
Array#flatメソッドを使うことで、多次元配列をフラットな配列に変換できます。 引数を指定しなかった場合は1段階のみのフラット化ですが、引数に渡す数値でフラット化する深さを指定できます。
const array = [[["A"], "B"], "C"];
// 引数なしは 1 を指定した場合と同じ
console.log(array.flat()); // => [["A"], "B", "C"]
console.log(array.flat(1)); // => [["A"], "B", "C"]
console.log(array.flat(2)); // => ["A", "B", "C"]
// すべてをフラット化するには Infinity を渡す
console.log(array.flat(Infinity)); // => ["A", "B", "C"]
配列から要素の削除
Array#splice
配列の先頭や末尾の要素を削除する場合はArray#shiftやArray#popで行えます。 しかし、配列の任意のインデックスの要素を削除できません。 配列の任意のインデックスの要素を削除するにはArray#spliceを利用できます。
const array = [];
array.splice(インデックス, 削除する要素数);
// 削除と同時に要素の追加もできる
array.splice(インデックス, 削除する要素数, ...追加する要素);
実行例
const array = ["a", "b", "c"];
// 1番目から1つの要素("b")を削除
array.splice(1, 1);
console.log(array); // => ["a", "c"]
console.log(array.length); // => 2
console.log(array[1]); // => "c"
// すべて削除
array.splice(0, array.length);
console.log(array.length); // => 0
Lengthプロパティへの代入
配列のすべての要素を削除することはArray#spliceで行えますが、 配列のlengthプロパティへの代入を利用した方法もあります。
空の配列に代入
その配列の要素を削除するのではなく、新しい空の配列を変数へ代入する方法です。
let array = [1, 2, 3];
console.log(array.length); // => 3
// 新しい配列で変数を上書き
array = [];
console.log(array.length); // => 0
constで宣言した配列の場合は変数に対して再代入できないため、この手法は使えません。 再代入をしたい場合はletまたはvarで変数宣言をする必要があります。
配列の入れ替え
先頭と末尾の配列の要素を入れ替える方法として以下の方法があります。
const array1 = [1, 2, 3];
const firstElement = array1.push(array1.shift());
console.log(firstElement); //=> Array [2, 3, 1]
破壊的・非破壊的メソッド
これまで紹介してきた配列を変更するメソッドには、破壊的なメソッドと非破壊的メソッドがあります。 破壊的なメソッドとは、配列オブジェクトそのものを変更し、変更した配列または変更箇所を返すメソッドです。 非破壊的メソッドとは、配列オブジェクトのコピーを作成してから変更し、そのコピーした配列を返すメソッドです。 破壊的メソッドを使用した場合、破壊的であることについてのコメントがあると親切です。
破壊的メソッドの例は下記の通りです。
メソッド | 返り値 |
---|---|
Array.prototype.pop | 配列の末尾の値 |
Array.prototype.push | 変更後の配列のlength |
Array.prototype.splice | 取り除かれた要素を含む配列 |
Array.prototype.reverse | 反転した配列 |
Array.prototype.shift | 配列の先頭の値 |
Array.prototype.sort | ソートした配列 |
Array.prototype.unshift | 変更後の配列のlength |
Array.prototype.copyWithin | 変更後の配列 |
Array.prototype.fill | 変更後の配列 |
一方、非破壊的メソッドは配列のコピーを作成するため、元々の配列に対して影響はありません。 破壊的メソッドを非破壊的なものにするには、受け取った配列をコピーしてから変更を加える必要があります。 JavaScriptにはcopyメソッドそのものは存在しませんが、配列をコピーする方法としてArray#sliceメソッドとArray#concatメソッドが利用されています。
const myArray = ["A", "B", "C"];
// `slice`は`myArray`のコピーを返す - `myArray.concat()`でも同じ
const copiedArray = myArray.slice();
myArray.push("D");
console.log(myArray); // => ["A", "B", "C", "D"]
// `array`のコピーである`copiedArray`には影響がない
console.log(copiedArray); // => ["A", "B", "C"]
// コピーであるため参照は異なる
console.log(copiedArray === myArray); // => false
配列を反復処理するメソッド
反復処理の中でもよく利用されるのがArray#forEach、Array#map、Array#filter、Array#reduceです。 どのメソッドも共通して引数にコールバック関数を受け取るため高階関数と呼ばれます。
メソッド | 処理 |
---|---|
Array#forEach | 配列の要素を先頭から順番にコールバック関数へ渡し、反復処理を行うメソッド |
Array#map | 配列の要素を順番にコールバック関数へ渡し、コールバック関数が返した値から新しい配列を返す非破壊的なメソッド |
Array#filter | 配列の要素を順番にコールバック関数へ渡し、コールバック関数がtrueを返した要素だけを集めた新しい配列を返す非破壊的なメソッド |
Array#reduce | 累積値(アキュムレータ)と配列の要素を順番にコールバック関数へ渡し、1つの累積値を返します |
文字列
文字列を作成するには、文字列リテラルを利用します。 文字列リテラルには"(ダブルクォート)、'(シングルクォート)、`(バッククォート)の3種類があります。
文字列へのアクセス
文字列の特定の位置にある文字にはインデックスを指定してアクセスできます。
const str = "文字列";
// 配列と同じようにインデックスでアクセスできる
console.log(str[0]); // => "文"
console.log(str[1]); // => "字"
console.log(str[2]); // => "列"
文字列の分解と結合
文字列を配列へ分解するにはString#splitメソッドを利用できます。 一方、配列の要素を結合して文字列にするにはArray#joinメソッドを利用できます。
String#splitメソッド
第一引数に指定した区切り文字で文字列を分解した配列を返します。
String#splitメソッドの第一引数には正規表現も指定できます。
// 文字間に1つ以上のスペースがある
const str = "a b c d";
// 1つ以上のスペースにマッチして分解する
const strings = str.split(/\s+/);
console.log(strings); // => ["a", "b", "c", "d"]
Array#joinメソッド
第一引数には区切り文字を指定し、その区切り文字で結合した文字列を返します。
文字列の長さ
String#lengthプロパティは文字列の要素数を返します。
文字列の比較
文字列の比較には===(厳密比較演算子)を利用します。 次の条件を満たしていれば同じ文字列となります。
- 文字列の要素であるCode Unitが同じ順番で並んでいるか
- 文字列の長さ(length)は同じか
console.log("文字列" === "文字列"); // => true
// 一致しなければfalseとなる
console.log("JS" === "ES"); // => false
// 文字列の長さが異なるのでfalseとなる
console.log("文字列" === "文字"); // => false
文字列の一部を取得
文字列からその一部を取り出したい場合には、String#sliceメソッドやString#substringメソッドが利用できます。
String#sliceメソッド
第一引数に開始位置、第二引数に終了位置を指定し、その範囲を取り出した新しい文字列を返します。
const str = "ABCDE";
console.log(str.slice(1)); // => "BCDE"
console.log(str.slice(1, 5)); // => "BCDE"
// マイナスを指定すると後ろからの位置となる
console.log(str.slice(-1)); // => "E"
// インデックスが1から4の範囲を取り出す
console.log(str.slice(1, 4)); // => "BCD"
// 第一引数 > 第二引数の場合、常に空文字列を返す
console.log(str.slice(4, 1)); // => ""
String#substringメソッド
第一引数に開始位置、第二引数に終了位置を指定し、その範囲を取り出して新しい文字列を返します。
const str = "ABCDE";
console.log(str.substring(1)); // => "BCDE"
console.log(str.substring(1, 5)); // => "BCDE"
// マイナスを指定すると0として扱われる
console.log(str.substring(-1)); // => "ABCDE"
// 位置:1から4の範囲を取り出す
console.log(str.substring(1, 4)); // => "BCD"
// 第一引数 > 第二引数の場合、引数が入れ替わる
// str.substring(1, 4)と同じ結果になる
console.log(str.substring(4, 1)); // => "BCD"
文字列の検索
文字列の検索方法として、大きく分けて文字列による検索と正規表現による検索があります。
- マッチした箇所のインデックスを取得
- マッチした文字列の取得
- マッチしたかどうかの真偽値を取得
文字列による検索
Stringオブジェクトには、指定した文字列で検索するメソッドが用意されています。 String#indexOfメソッドとString#lastIndexOfメソッドは、指定した文字列で検索し、その文字列が最初に現れたインデックスを返します。 * 文字列.indexOf("検索文字列"): 先頭から検索し、指定された文字列が最初に現れたインデックスを返す * 文字列.lastIndexOf("検索文字列"): 末尾から検索し、指定された文字列が最初に現れたインデックスを返す
// 検索対象となる文字列
const str = "にわにはにわにわとりがいる";
// indexOfは先頭から検索しインデックスを返す - "**にわ**にはにわにわとりがいる"
// "にわ"の先頭のインデックスを返すため 0 となる
console.log(str.indexOf("にわ")); // => 0
// lastIndexOfは末尾から検索しインデックスを返す- "にわにはにわ**にわ**とりがいる"
console.log(str.lastIndexOf("にわ")); // => 6
// 指定した文字列が見つからない場合は -1 を返す
console.log(str.indexOf("未知のキーワード")); // => -1
文字列にマッチした文字列の取得
文字列を検索してマッチした文字列は、検索文字列そのものになるので自明です。
const str = "JavaScript";
const searchWord = "Script";
const index = str.indexOf(searchWord);
if (index !== -1) {
console.log(`${searchWord}が見つかりました`); //=>実行される
} else {
console.log(`${searchWord}は見つかりませんでした`);
}
真偽値に取得
次の3つのメソッドはES2015で導入されました。
- String#startsWith(検索文字列): 検索文字列が先頭にあるかの真偽値を返す
- String#endsWith(検索文字列): 検索文字列が末尾にあるかの真偽値を返す
- String#includes(検索文字列): 検索文字列を含むかの真偽値を返す
具体例
// 検索対象となる文字列
const str = "にわにはにわにわとりがいる";
// startsWith - 検索文字列が先頭ならtrue
console.log(str.startsWith("にわ")); // => true
console.log(str.startsWith("いる")); // => false
// endsWith - 検索文字列が末尾ならtrue
console.log(str.endsWith("にわ")); // => false
console.log(str.endsWith("いる")); // => true
// includes - 検索文字列が含まれるならtrue
console.log(str.includes("にわ")); // => true
console.log(str.includes("いる")); // => true
文字列の置換と削除
文字列の一部を置換したり削除するにはString#replaceメソッドを利用します。 replaceメソッドは、文字列から第一引数の検索文字列または正規表現にマッチする部分を、第二引数の置換文字列へ置換します。 第一引数には、文字列と正規表現を指定できます。
サンプルコード
const str = "文字列";
// "文字"を""(空文字列)へ置換することで"削除"を表現
const newStr = str.replace("文字", "");
console.log(newStr); // => "列"
replaceメソッドには正規表現も指定できます。 gフラグを有効化した正規表現を渡すことで、文字列からパターンにマッチするものをすべて置換できます。
// 検索対象となる文字列
const str = "にわにはにわにわとりがいる";
// 文字列を指定した場合は、最初に一致したものだけが置換される
console.log(str.replace("にわ", "niwa")); // => "niwaにはにわにわとりがいる"
// `g`フラグなし正規表現の場合は、最初に一致したものだけが置換される
console.log(str.replace(/にわ/, "niwa")); // => "niwaにはにわにわとりがいる"
// `g`フラグあり正規表現の場合は、繰り返し置換を行う
console.log(str.replace(/にわ/g, "niwa")); // => "niwaにはniwaniwaとりがいる"
String#replaceAllメソッドも利用できます。 String#replaceメソッドでは、最初に一致したものだけが置換されますが、String#replaceAllメソッドでは一致したものがすべて置換されます。
// 検索対象となる文字列
const str = "???";
// replaceメソッドに文字列を指定した場合は、最初に一致したものだけが置換される
console.log(str.replace("?", "!")); // => "!??"
// replaceAllメソッドに文字列を指定した場合は、一致したものがすべて置換される
console.log(str.replaceAll("?", "!")); // => "!!!"
// replaceメソッドの場合は、正規表現の特殊文字はエスケープが必要となる
console.log(str.replace(/\?/g, "!")); // => "!!!"
// replaceAllメソッドにも正規表現を渡せるが、この場合はエスケープが必要となるためreplaceと同じ
console.log(str.replaceAll(/\?/g, "!")); // => "!!!"
複雑な置換処理
replaceメソッドとreplaceAllメソッドでは、キャプチャした文字列を利用して複雑な置換処理もできます。 例として、2017-03-01を2017年03月01日に置換する処理を書いてみましょう。 /(\d{4})-(\d{2})-(\d{2})/gという正規表現が"2017-03-01"という文字列にマッチします。 コールバック関数のyear、month、dayにはそれぞれキャプチャした文字列が入り、 マッチした文字列全体がコールバック関数の返り値に置換されます。
function toDateJa(dateString) {
// パターンにマッチしたときのみ、コールバック関数で置換処理が行われる
return dateString.replace(/(\d{4})-(\d{2})-(\d{2})/g, (all, year, month, day) => {
// `all`には、マッチした文字列全体が入っているが今回は利用しない
// `all`が次の返す値で置換されるイメージ
return `${year}年${month}月${day}日`;
});
}
// マッチしない文字列の場合は、そのままの文字列が返る
console.log(toDateJa("本日ハ晴天ナリ")); // => "本日ハ晴天ナリ"
// マッチした場合は置換した結果を返す
console.log(toDateJa("今日は2017-03-01です")); // => "今日は2017年03月01日です"
正規表現オブジェクト
文字列による検索では、固定の文字列にマッチするものしか検索できません。 一方で正規表現による検索では、あるパターン(規則性)にマッチするという柔軟な検索ができます。
次の文字は特殊文字と呼ばれ、特別な意味を持ちます。 特殊文字として解釈されないように入力する場合には\(バックスラッシュ)でエスケープする必要があります。
正規表現オブジェクトを作成するには、正規表現リテラルとRegExpコンストラクタを使う2つの方法があります。
// 正規表現リテラルで正規表現オブジェクトを作成
const patternA = /パターン/フラグ;
// `RegExp`コンストラクタで正規表現オブジェクトを作成
const patternB = new RegExp("パターン文字列", "フラグ");
正規表現リテラルは、/と/のリテラル内に正規表現のパターンを書くことで、正規表現オブジェクトを作成できます。
正規表現オブジェクトを作成するもうひとつの方法としてRegExpコンストラクタがあります。
正規表現リテラルとRegExpコンストラクタの違い
正規表現リテラルとRegExpコンストラクタの違いとして、正規表現のパターンが評価されるタイミングの違いがあります。 正規表現リテラルは、ソースコードをロード(パース)した段階で正規表現のパターンが評価されます。 一方で、RegExpコンストラクタでは通常の関数と同じように、RegExpコンストラクタを呼び出すまで正規表現のパターンは評価されません。
正規表現リテラルはコードを書いた時点で決まったパターンの正規表現オブジェクトを作成する構文です。 RegExpコンストラクタは変数と組み合わせるなど、実行時に変わることがあるパターンの正規表現オブジェクトを作成できます。
// 3つの連続するスペースなどにマッチする正規表現
const pattern = /\s{3}/;
const spaceCount = 3;
// `/\s{3}/`の正規表現を文字列から作成する
// "\"がエスケープ文字であるため、"\"自身を文字列として書くには、"\\"のように2つ書く
const pattern = new RegExp(`\\s{${spaceCount}}`);
正規表現のパターンに変数を利用する場合などは、RegExpコンストラクタを利用します。
正規表現による検索
正規表現による検索は、正規表現オブジェクトと対応したStringオブジェクトまたはRegExpオブジェクトのメソッドを利用します。
正規表現によるインデックスの取得
String#indexOfメソッドの正規表現版ともいえるString#searchメソッドがあります。 searchメソッドは正規表現のパターンにマッチした箇所のインデックスを返し、マッチする文字列がない場合は-1を返します。
次のコードでは、数字が3つ連続しているかを検索し、該当した箇所のインデックスを返しています。 \dは、1文字の数字(0から9)にマッチする特殊文字です。
const str = "ABC123EFG";
const searchPattern = /\d{3}/;
console.log(str.search(searchPattern)); // => 3
正規表現によるマッチした文字列の取得
文字列による検索では、検索した文字列そのものがマッチした文字列になります。 しかし、searchメソッドの正規表現による検索は、正規表現パターンによる検索であるため、検索してマッチした文字列の長さは固定ではありません。
const str = "abc123def";
// 連続した数字にマッチする正規表現
const searchPattern = /\d+/;
const index = str.search(searchPattern); // => 3
// `index` だけではマッチした文字列の長さがわからない
str.slice(index, index + マッチした文字列の長さ); // マッチした文字列は取得できない
マッチした文字列を取得するString#matchメソッドとString#matchAllメソッドが用意されています。
マッチした文字列の取得
String#matchメソッド
正規表現の/パターン/が"文字列"にマッチすると、マッチした文字列に関する情報を返すメソッドです。
String#matchメソッドは正規表現のgフラグなしのパターンで検索した場合、最初にマッチしたものが見つかった時点で検索が終了します。次のコードの/[a-zA-Z]+/という正規表現はaからZのどれかの文字が1つ以上連続しているものにマッチします。 この正規表現にマッチした文字列は、返り値の配列からインデックスアクセスで取得できます。 gフラグなしでは、最初にマッチしたものを見つけた時点で検索が終了するので、返り値の配列には1つの要素しか含まれていません。
const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/;
// gフラグなしでは、最初の結果のみを含んだ特殊な配列を返す
const results = str.match(alphabetsPattern);
console.log(results.length); // => 1
// マッチした文字列はインデックスでアクセスできる
console.log(results[0]); // => "ABC"
// マッチした文字列の先頭のインデックス
console.log(results.index); // => 0
// 検索対象となった文字列全体
console.log(results.input); // => "ABC あいう DE えお"
String#matchメソッドは正規表現のgフラグありのパターンで検索した場合、マッチしたすべての文字列を含んだ配列を返します。
const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/g;
// gフラグありでは、すべての検索結果を含む配列を返す
const resultsWithG = str.match(alphabetsPattern);
console.log(resultsWithG.length); // => 2
console.log(resultsWithG[0]); // => "ABC"
console.log(resultsWithG[1]); // => "DE"
// indexとinputはgフラグありの場合は追加されない
console.log(resultsWithG.index); // => undefined
console.log(resultsWithG.input); // => undefined
マッチした文字列の一部を取得
String#matchメソッドとString#matchAllメソッドでキャプチャリングを行うことで、正規表現でマッチした部分だけを取り出せます。
String#matchAllメソッド
正規表現のgフラグを使い繰り返し文字列にマッチする場合には、String#matchAllメソッドを利用します。 String#matchメソッドは繰り返しマッチした場合に、それぞれ個別のマッチした情報を取得できないためです。
次のコードでは、ES数字の数字(\d+)にマッチする部分を取り出しています。
// "ES(数字+)"にマッチするが、欲しい文字列は数字の部分のみ
const pattern = /ES(\d+)/g;
// iteratorを返す
const matchesIterator = "ES2015、ES2016、ES2017".matchAll(pattern);
for (const match of matchesIterator) {
// マッチした要素ごとの情報を含んでいる
// 0番目はマッチした文字列全体、1番目がキャプチャの1番目である数字
console.log(`match: "${match[0]}", capture1: ${match[1]}, index: ${match.index}, input: "${match.input}"`);
}
// 次の順番でコンソールに出力される
// match: "ES2015", capture1: 2015, index: 0, input: "ES2015、ES2016、ES2017"
// match: "ES2016", capture1: 2016, index: 7, input: "ES2015、ES2016、ES2017"
// match: "ES2017", capture1: 2017, index: 14, input: "ES2015、ES2016、ES2017"
真偽値の取得
正規表現オブジェクトを使って、そのパターンにマッチするかをテストするには、RegExp#testメソッドを利用できます。 * String#startsWith: /^パターン/.test(文字列) ** ^ は先頭に一致する特殊文字 * String#endsWith: /パターン$/.test(文字列) ** $ は末尾に一致する特殊文字 * String#includes: /パターン/.test(文字列)
// 検索対象となる文字列
const str = "にわにはにわにわとりがいる";
// ^ - 検索文字列が先頭ならtrue
console.log(/^にわ/.test(str)); // => true
console.log(/^いる/.test(str)); // => false
// $ - 検索文字列が末尾ならtrue
console.log(/にわ$/.test(str)); // => false
console.log(/いる$/.test(str)); // => true
// 検索文字列が含まれるならtrue
console.log(/にわ/.test(str)); // => true
console.log(/いる/.test(str)); // => true
正規表現の注意
正規表現は柔軟で便利ですが、コード上から意図が消えてしまいやすいです。 正規表現を扱う際にはコメントや変数名で具体的な意図を補足したほうがよいでしょう。
関数とスコープ
定義された関数はそれぞれのスコープを持っています。スコープとは変数や関数の引数などを参照できる範囲を決めるものです。 JavaScriptでは、新しい関数を定義するとその関数にひもづけられた新しいスコープが作成されます。
スコープ
スコープとは変数の名前や関数などの参照できる範囲を決めるものです。 スコープの中で定義された変数はスコープの内側でのみ参照でき、スコープの外側からは参照できません。
function fn() {
const x = 1;
// fn関数のスコープ内から`x`は参照できる
console.log(x); // => 1
}
fn();
// fn関数のスコープ外から`x`は参照できないためエラー
console.log(x); // => ReferenceError: x is not defined
このような、関数によるスコープのことを関数スコープと呼びます。 一方、スコープが異なれば同じ名前で変数を宣言できます。 次のコードでは、fnA関数とfnB関数という異なるスコープで、それぞれ変数xを定義できていることがわかります。
ブロックスコープ
{と}で囲んだ範囲をブロックと呼びます。 ブロックの中で宣言した変数は外から参照できません。
// if文のブロック内で定義した変数はブロックスコープの中でのみ参照できる
if (true) {
const x = "inner";
console.log(x); // => "inner"
}
console.log(x); // => ReferenceError: x is not defined
スコープチェーン
関数やブロックはネスト(入れ子)して書けますが、同様にスコープもネストできます。
{
// OUTERブロックスコープ
const x = "x";
{
// INNERブロックスコープからOUTERブロックスコープの変数を参照できる
console.log(x); // => "x"
}
}
内側から外側のスコープへと順番に変数が定義されているか探す仕組みのことをスコープチェーンと呼びます。
グローバルスコープ
暗黙的なグローバルスコープ(大域スコープ)と呼ばれるスコープが存在します。 グローバルスコープとは名前のとおりもっとも外側にあるスコープで、プログラム実行時に暗黙的に作成されます。
グローバルスコープで定義した変数はグローバル変数と呼ばれ、グローバル変数はあらゆるスコープから参照できる変数となります。
// グローバル変数はどのスコープからも参照できる
const globalVariable = "グローバル";
// ブロックスコープ
{
// ブロックスコープ内には該当する変数が定義されてない -> 外側のスコープへ
console.log(globalVariable); // => "グローバル"
}
// 関数スコープ
function fn() {
// 関数ブロックスコープ内には該当する変数が定義されてない -> 外側のスコープへ
console.log(globalVariable); // => "グローバル"
}
fn();
関数宣言と巻き上げ
functionキーワードを使った関数宣言はもっとも近い関数またはグローバルスコープの先頭に巻き上げられます。
関数とthis
この章ではthisという特殊な動作をするものはメソッドの中で利用しますが、thisは読み取り専用のグローバル変数のようなものでどこにでも書けます。 thisの参照先(評価結果)は条件によって異なります。
- 実行コンテキストにおけるthis
- コンストラクタにおけるthis
- 関数とメソッドにおけるthis
- Arrow Functionにおけるthis
クラス
「クラス」と一言にいってもさまざまであるため、ここでは構造、動作、状態を定義できるものを指すことにします。 概念を示す場合はクラスと呼びます。 クラスとは動作や状態を定義した構造です。 クラスからはインスタンスと呼ばれるオブジェクトを作成でき、インスタンスはクラスに定義した動作を継承し、状態は動作によって変化します。
クラスの定義
classキーワードを使い、class クラス名{ }のようにクラスの構造を定義できます。 クラスは必ずコンストラクタを持ち、constructorという名前のメソッドとして定義します。 コンストラクタとは、そのクラスからインスタンスを作成する際にインスタンスに関する状態の初期化を行うメソッドです。
コンストラクタ関数内で、何も処理がない場合はコンストラクタの記述を省略できます。 省略した場合でも自動的に空のコンストラクタが定義されるため、クラスにはコンストラクタが必ず存在します。
class MyClassA {
constructor() {
// コンストラクタの処理が必要なら書く
}
}
// コンストラクタの処理が不要な場合は省略できる
class MyClassB {
}
クラスのインスタンス化
クラスはnew演算子でインスタンスであるオブジェクトを作成できます。 class構文で定義したクラスからインスタンスを作成することをインスタンス化と呼びます。 インスタンスが指定したクラスから作成されたものかを判定するにはinstanceof演算子が利用できます。
class MyClass {
}
// `MyClass`をインスタンス化する
const myClass = new MyClass();
// 毎回新しいインスタンス(オブジェクト)を作成する
const myClassAnother = new MyClass();
// それぞれのインスタンスは異なるオブジェクト
console.log(myClass === myClassAnother); // => false
// クラスのインスタンスかどうかは`instanceof`演算子で判定できる
console.log(myClass instanceof MyClass); // => true
console.log(myClassAnother instanceof MyClass); // => true
クラスの使用例
class Point {
// 2. コンストラクタ関数の仮引数として`x`には`3`、`y`には`4`が渡る
constructor(x, y) {
// 3. インスタンス(`this`)の`x`と`y`プロパティにそれぞれ値を設定する
this.x = x;
this.y = y;
// コンストラクタではreturn文は書かない
}
}
// 1. コンストラクタを`new`演算子で引数とともに呼び出す
const point = new Point(3, 4);
// 4. `Point`のインスタンスである`point`の`x`と`y`プロパティには初期化された値が入る
console.log(point.x); // => 3
console.log(point.y); // => 4
コンストラクタは初期化処理を書く場所であるため、return文で値を返すべきではありません。
クラス名は大文字ではじめる
JavaScriptでは慣習としてクラス名には大文字ではじまる名前をつけます。
静的メソッド
インスタンスメソッドは、クラスをインスタンス化して利用します。 一方、クラスをインスタンス化せずに利用できる静的メソッド(クラスメソッド)もあります。 静的メソッドの定義方法はメソッド名の前に、staticをつけるだけです。
静的メソッドは、クラスのインスタンスを作成する処理やクラスに関係する処理を書くために利用されます。
継承
extendsキーワードを使うことで既存のクラスを継承できます。 継承とは、クラスの構造や機能を引き継いだ新しいクラスを定義することです。
継承したクラスの定義
class構文の右辺にextendsキーワードで継承元となる親クラス(基底クラス)を指定することで、 親クラスを継承した子クラス(派生クラス)を定義できます。
例外処理
try_catch構文
try_catch構文は例外が発生しうるブロックをマークし、例外が発生したときの処理を記述するための構文です。 次のコードでは、tryブロックで例外が発生し、catch節の処理が実行され、最後にfinally節の処理が実行されます。
try {
console.log("try節:この行は実行されます");
// 未定義の関数を呼び出してReferenceError例外が発生する
undefinedFunction();
// 例外が発生したため、この行は実行されません
} catch (error) {
// 例外が発生したあとはこのブロックが実行される
console.log("catch節:この行は実行されます");
console.log(error instanceof ReferenceError); // => true
console.log(error.message); // => "undefinedFunction is not defined"
} finally {
// このブロックは例外の発生に関係なく必ず実行される
console.log("finally節:この行は実行されます");
}
throw文
throw文を使うとユーザーが例外を投げることができます。 例外として投げられたオブジェクトは、catch節で関数の引数のようにアクセスできます。
// 例外を投げる
throw new Error("例外が投げられました");
} catch (error) {
// catch節のスコープでerrorにアクセスできる
console.log(error.message); // => "例外が投げられました"
}
非同期処理:コールバック/Promise/Async関数
多くのプログラミング言語にはコードの評価の仕方として、同期処理(sync)と 非同期処理(async) という大きな分類があります。 今まで書いていたコードは同期処理と呼ばれているものです。 同期処理ではコードを順番に処理していき、ひとつの処理が終わるまで次の処理は行いません。 同期処理では実行している処理はひとつだけとなるため、とても直感的な動作となります。
- HTTPのリクエスト
- タイマーによる処理
- ブラウザ操作におけるイベント処理
- DATABASEの操作
など色々なところで非同期処理を行うことで効率のいいアプリケーションを作成することができます。
非同期処理
非同期処理はコードを順番に処理していきますが、ひとつの非同期処理が終わるのを待たずに次の処理を評価します。 つまり、非同期処理では同時に実行している処理が複数あります。
JavaScriptにおいて非同期処理の代表的な関数としてsetTimeout関数があります。 setTimeout関数はdelayミリ秒後に、コールバック関数を呼び出すようにタイマーへ登録する非同期処理です。
setTimeout関数の使用例
// 指定した`timeout`ミリ秒経過するまで同期的にブロックする関数
function blockTime(timeout) {
const startTime = Date.now();
while (true) {
const diffTime = Date.now() - startTime;
if (diffTime >= timeout) {
return; // 指定時間経過したら関数の実行を終了
}
}
}
console.log("1. setTimeoutのコールバック関数を10ミリ秒後に実行します");
setTimeout(() => {
console.log("3. ブロックする処理を開始します");
blockTime(1000); // 他の処理を1秒間ブロックする
console.log("4. ブロックする処理が完了しました");
}, 10);
// ブロックする処理は非同期なタイミングで呼び出されるので、次の行が先に実行される
console.log("2. 同期的な処理を実行します");
非同期処理は並行処理(concurrent)として扱われます。 並行処理とは、処理を一定の単位ごとに分けて処理を切り替えながら実行することです。
非同期処理と例外処理
非同期処理では、try_catch構文を使っても非同期的に発生した例外をキャッチできません そのため、setTimeout関数のコールバック関数における例外は、次のようにコールバック関数内で同期的なエラーとしてキャッチする必要があります。
// 非同期処理の外
setTimeout(() => {
// 非同期処理の中
try {
throw new Error("エラー");
} catch (error) {
console.log("エラーをキャッチできる");
}
}, 10);
console.log("この行は実行されます");
コールバック関数の地獄
前述したsetTimeout関数のみでタイミングをずらした非同期処理を実現したい場合下記のようなコードになります。
setTimeout(() =>{
console.log(3);
setTimeout(() =>{
console.log(2);
setTimeout(() => {
console.log(1);
}, 1000);
}, 1000);
}, 1000);
上記のように大量のネスト構造によりコードの可読性、保守性が著しく悪いコードとなります。 これを解消するために生まれた非同期関数がPromiseです。 Promiseは連続した非同期処理をコンパクトに記述できます。 上記の関数をPromiseを用いて書き直すと以下のようになります。
new Promise(resolve => {
setTimeout(() => {
console.log(3)
resolve();
}, 1000)
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(2)
resolve();
}, 1000)
}
});
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve();
}, 1000)
}
});
});
ネストの問題は解消されましたが、このコードは冗長であり記述量も多いためコードの質が良いと言えません。 そこで後述するasync/await関数を用います。
func = async() =>{ //非同期関数宣言
await log(3);
await log(2);
await log(1);
};
log = (num) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(num);
resolve();
}, 1000);
});
}
func();
async/await関数とPromiseを用いることで複雑な非同期処理を簡易的に書くことができます。
Promise
PromiseはES2015で導入された非同期処理の結果を表現するビルトインオブジェクトです。 Promiseオブジェクトは、非同期処理の完了 (もしくは失敗) の結果およびその結果の値を表します。
Promiseオブジェクトは3つの内部状態を持ちます。 * pending(保留): まだ非同期処理は終わっていない(成功も失敗もしていない) * fulfilled(成功): 非同期処理が正常に終了した * rejected(拒否): 非同期処理が失敗した
初期状態はpendingで、一度fulfilledまたはrejectedになったらそれ以降は状態は変わらず、非同期処理の終了時に返す値もそれ以降は変わりません。
Promiseのコンストラクタ
Promiseのコンストラクターは、関数を引数に取ります。
- 関数は2つの関数(resolve, reject)を引数に取る
- 関数が例外を投げた場合も状態がrejectedになり、投げた値がPromiseオブジェクトが保持する値になる
Promise を利用する場合、関数の return にnew Promise() を指定し、コールバック関数を Promise に登録します。
function promiseFunc(pay) {
// Promise を返す
return new Promise(function(resolve, reject) {
if ( /*関数の成功条件*/ ) {
// 成功したとき
resolve()
} else {
// 失敗したとき
reject()
}
});
}
関数の return にnew Promise(function()) と記述しています。Promise インスタンスを返却するように記述すると、Promiseが利用できます。
Promiseのthen()
Promise の処理が終了したのち、結果を取得するにはthen()を利用します。 then() は、 Promise のインスタンスの状態がfulfilled となったときに実行する関数を登録できるインスタンスメソッドです。
- onFulfilled : fulfilled の状態のとき(resolveが呼ばれたとき)に実行される関数
- onRejected : rejected の状態のとき(rejectが呼ばれたとき)に実行される関数
例)購入処理に0.5秒かかる非同期関数buy()を実行した場合
function buy(pay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (pay >= 100) {
console.log("100円の商品を購入しました");
resolve(pay - 100);
} else {
reject("お金が足りないよ");
}
}, 500);
});
}
// 実行部分 と then 部分
console.log(1);
buy(300)
.then(function(change) {
console.log(`お釣りは${change}円です`)
})
console.log(3);
実行結果
実行結果を確認すると、「お釣りは 200円です」と表示されており、resolve()の引数を受け取れていることが確認できるとわかります。Promiseのcatch()
Promise のインスタンスのエラー処理には catch()を利用します。 catch() とは Promise のインスタンスの状態がrejected となったときに実行する関数を登録するインスタンスメソッドです。
Promise を利用したコードは then() とcatch()で正常処理、エラー処理を切り替えることができます。
Promiseのチェーンメソッド
Promiseのthen(),catch() はつなげて利用することもできます。
function buy(pay) {
return new Promise((resolve, reject) => {
setTimeout(function() {
if (pay >= 100) {
console.log("100円の商品を購入しました");
resolve(pay - 100);
} else {
reject(Error("error"));
}
}, 500);
});
}
buy(550)
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.then(change => {
console.log(`お釣りは${change}円です`);
return buy(change);
})
.catch(() => console.log("お金が足りないよ"));
結果
100円の商品を購入しました
お釣りは450円です
100円の商品を購入しました
お釣りは350円です
100円の商品を購入しました
お釣りは250円です
100円の商品を購入しました
お釣りは150円です
100円の商品を購入しました
お釣りは50円です
お金が足りないよ
ソースコード中では buy() を 8回も呼んでいますが、購入処理が行われたのは5回だけです。 お金が足りなくなったので途中の buy() の中でreject() が呼び出され、それ以降の then() の処理が行われることはなく、catch()でエラー処理されます。
Promiseのresolve()
Promiseのresoleve()はメソッドがFullfilled(成功) 状態になったPromiseインスタンスを生成します。 Promise.resolveメソッドはnew Promiseの簡易的記述構文です。
const fulfilledPromise = Promise.resolve();
// 下の宣言と上の宣言は同じ意味
const fulfilledPromise = new Promise((resolve) => {
resolve();
});
Promiseのreject()
Promiseのreject()は Rejected(失敗) の状態となったPromiseインスタンスを生成します。 Promise.resolveメソッドはnew Promiseの簡易的記述構文です。
const rejectedPromise = Promise.reject(new Error("エラー"));
// 下の宣言と上の宣言は同じ意味
const rejectedPromise = new Promise((resolve, reject) => {
reject(new Error("エラー"));
});
async/await関数
ES2017では、Async関数という非同期処理を行う関数を定義する構文が導入されました。 Async関数は次のように関数の前にasyncをつけることで定義できます。 このdoAsync関数は常にPromiseインスタンスを返します。
async function doAsync() {
return "値";
}
// doAsync関数はPromiseを返す
doAsync().then(value => {
console.log(value); // => "値"
});
Async関数内ではawait式というPromiseの非同期処理が完了するまで待つ構文が利用できます。 await式を使うことで非同期処理を同期処理のように扱えるため、Promiseチェーンで実現していた処理の流れを読みやすく書けます。
Async関数の定義とPromise
Async関数は以下のように定義できます。
// 関数宣言のAsync関数版
async function fn1() {}
// 関数式のAsync関数版
const fn2 = async function() {};
// アロー関数のAsync関数版
const fn3 = async() => {};
// メソッドの短縮記法のAsync関数版
const obj = { async method() {} };
Async関数は通常の関数と+αで以下の特徴を持ちます。 * Async Functionは必ずPromiseインスタンスを返す * Async Function内ではawait式が利用できる
またasync関数はPromiseを必ず返します。 下記のコードではAsync関数がそれぞれの返り値によってどのようなPromiseインスタンスを返すかを確認できます。
// 1. resolveFnは値を返している
// 何もreturnしていない場合はundefinedを返したのと同じ扱いとなる
async function resolveFn() {
return "返り値";
}
resolveFn().then(value => {
console.log(value); // => "返り値"
});
// 2. rejectFnはPromiseインスタンスを返している
async function rejectFn() {
return Promise.reject(new Error("エラーメッセージ"));
}
// rejectFnはRejectedなPromiseを返すのでcatchできる
rejectFn().catch(error => {
console.log(error.message); // => "エラーメッセージ"
});
// 3. exceptionFnは例外を投げている
async function exceptionFn() {
throw new Error("例外が発生しました");
// 例外が発生したため、この行は実行されません
}
// Async Functionで例外が発生するとRejectedなPromiseが返される
exceptionFn().catch(error => {
console.log(error.message); // => "例外が発生しました"
});
await式
await式はAsync関数下で一般的には用いられます。
async function asyncMain() {
// PromiseがFulfilledまたはRejectedとなるまで待つ
await Promiseインスタンス;
// Promiseインスタンスの状態が変わったら処理を再開する
}
await式では非同期処理を実行して完了するまで、次の行を実行しません。そのためawait式を使うことで非同期処理が同期処理のように上から下へと順番に実行するような処理順で書けます。
// async functionは必ずPromiseを返す
async function doAsync() {
// 非同期処理
}
async function asyncMain() {
// doAsyncの非同期処理が完了するまでまつ
await doAsync();
// 次の行はdoAsyncの非同期処理が完了されるまで実行されない
console.log("この行は非同期処理が完了後に実行される");
}
await式を用いた非同期処理の記述工夫
await式を使うことで、try...catch構文のように非同期処理を同期処理と同じ構文を使って扱えます。
次のコードでは、await式で発生した例外をtry...catch構文でキャッチしています。 そのため、asyncMain関数はResolvedなPromiseを返し、catchメソッドのコールバック関数は呼び出されません。
async function asyncMain() {
// await式のエラーはtry...catchできる
try {
// `await`式で評価した右辺のPromiseがRejectedとなったため、例外がthrowされる
const value = await Promise.reject(new Error("エラーメッセージ"));
// await式で例外が発生したため、この行は実行されません
} catch (error) {
console.log(error.message); // => "エラーメッセージ"
}
}
// asyncMainはResolvedなPromiseを返す
asyncMain().catch(error => {
// すでにtry...catchされているため、この行は実行されません
});
Promiseのチェーンメソッドをawait式での記述
Async関数とawait式を使うことでPromiseチェーンとして表現していた非同期処理を同期処理のような見た目で書けます。 await式を使ってリソースが取得できるまで待ち、その結果を変数resultsに追加していくという形で逐次処理が実装されます。
function dummyFetch(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path.startsWith("/resource")) {
resolve({ body: `Response body of ${path}` });
} else {
reject(new Error("NOT FOUND"));
}
}, 1000 * Math.random());
});
}
// リソースAとリソースBを順番に取得する
async function fetchAB() {
const results = [];
const responseA = await dummyFetch("/resource/A");
results.push(responseA.body);
const responseB = await dummyFetch("/resource/B");
results.push(responseB.body);
return results;
}
// リソースを取得して出力する
fetchAB().then((results) => {
console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});
実行結果
Map/Setオブジェクト
現状、必要の遭遇をしていないため割愛