JavaScript ES6(ECMAScript 2015)で追加された機能

以下は ES6(ECMAScript 2015)で追加された機能(let、const、テンプレートリテラル、デフォルト引数、Rest パラメータ、スプレッド構文、分割代入、イテレータ、for..of、Array.from()、アロー関数、モジュール、Symbol、Map、Set、クラス構文)の基本的な使い方についての覚書です。

ブラウザの対応状況

参考になるサイト:

関連ページ:ネイティブの JavaScript を使う(ES5 までの機能)

作成日:2020年5月6日

変数の宣言

ES6(ECMAScript 2015)から新たに変数の宣言方法として let と const が導入されました。

従来からある var の代わりにこれらのキーワードを使うことで変数を二重に定義してしまうなどのミスを防ぐことができます。

キーワード 説明
let ブロックスコープの局所的な変数を宣言。値を再代入することはできますが、もう一度宣言(再宣言)することはできません。
const ブロックスコープの局所的な変数を宣言しますが、再代入による変更はできず、再宣言もできません。宣言時に値(初期値)を設定する必要があります。

変数の宣言を行うキーワードは var、const、let の3つになり、ほとんどの場合、var は const または let に置き換えることができます。

var には同じ名前の変数を再定義できてしまうなどの問題があるため、変数を宣言する場合、const を使って定義できないかを検討し、できない場合は let を使うことが推奨されています。

let

let はブロックスコープの局所的な変数を宣言します(自身が定義されたブロックがスコープになります)。

ブロックの外からアクセスしようとするとエラー(ReferenceError)になります。

if(true) {
  let foo = "foo";
  console.log(foo);  //foo
}
console.log(foo);  //Uncaught ReferenceError: foo is not defined
//ブロックの外からアクセスしようとするとエラーになります。

値を再代入することはできますが、もう一度宣言(再宣言)することはできません。

再宣言するとエラー(SyntaxError)になります。以下の例の場合、8行目の記述があると3行目と6行目はエラーのため実行されません。

let foo;  //宣言と初期化
foo = "foo";  //値の代入
console.log(foo);  //foo
        
foo = "foo2";  //値の再代入
console.log(foo);   //foo2
        
let foo = "foo3";  //Uncaught SyntaxError: Identifier 'foo' has already been declared
//同じ変数を再度宣言するとエラーになります。

変数を宣言より前で参照すると ReferenceError になります(var の場合は変数の巻き上げによりエラーにならず以下の場合だと undefined と出力されます)。

function myfunc() {
  console.log(foo); //Uncaught ReferenceError: Cannot access 'foo' before initialization
  let foo = 'foo';
} 
myfunc(); 

トップレベルで let で宣言してもグローバル変数(window オブジェクトのプロパティ)にはなりません。

let foo = 'foo';
console.log(window.foo);  //undefined

var bar = 'bar';  //var キーワードで宣言
console.log(window.bar);  //bar

boo = 'boo';  //キーワードなしで宣言。
// "use strict" を指定している場合はエラー(ReferenceError: boo is not defined)
console.log(window.boo);   //boo

MDN let

const

const は let と同様、ブロックスコープの局所的な変数を宣言しますが、再代入による値の変更はできず、再宣言もできません。

let と同様、ブロックの外からアクセスしようとするとエラー(ReferenceError)になります。

if(true) {
  const foo = "foo";
  console.log(foo);  //foo
}
console.log(foo);  //Uncaught ReferenceError: foo is not defined
//ブロックの外からアクセスしようとするとエラーになります。

宣言と初期化の際は値を代入(設定)する必要があります。

const foo;  //エラー Uncaught SyntaxError: Missing initializer in const declaration

値がない(且つ変更できない)変数を作成するには、値に null を指定します。null は「値がない」という値です。

const foo = null;  //値がなく変更できない変数
console.log(foo);  //null

再代入できません。値を変更しようとするとエラー(TypeError)になります。

const foo = 'foo';  //値を代入して初期化
foo = 'new foo'; //エラー Uncaught TypeError: Assignment to constant variable.

以下の場合、for 文の i++ が再代入になるのでエラーになります。

const array = [ 'a', 'b', 'c' ];
//const i の後に i++ があり再代入になる Uncaught TypeError: Assignment to constant variable.
for(const i = 0; i < array.length; i++) {  //エラー
  console.log(array[i]);
}

//以下のように let であればOK
for(let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

//以下の場合は問題ありません(for ループごとに新しいブロックとなるため)
for(const val of array ) {  
  console.log(val);
} 

let と同様、再宣言するとエラー(SyntaxError)になります。

const foo = 'foo';
const foo = 'new foo'; //Uncaught SyntaxError: Identifier 'foo' has already been declared

以下の場合は for ループごとに別々の新しいブロック { } で実行されるため再宣言にはならず問題ありません。

const array = [ 'a', 'b', 'c' ];
  
for(let i = 0; i < array.length; i++) {
  const value = array[i];
  console.log(value);
}

let と同様、トップレベルで const で宣言してもグローバル変数(window オブジェクトのプロパティ)にはなりません。

const foo = 'foo';
console.log(window.foo);  //undefined

var bar = 'bar';  //var キーワードで宣言
console.log(window.bar);  //bar

boo = 'boo';  //キーワードなしで宣言。
console.log(window.boo);   //boo
// "use strict" を指定している場合はエラー(ReferenceError: boo is not defined)

const は定数のような変数ですが、正確には定数ではありません。

const キーワードを使ってプリミティブな値(数値や文字列など)で初期化すれば、ほぼ定数と同じです。

const FOO = 'FOO';
//FOO は常に'FOO'という値で変更できない

但し、オブジェクトや配列なども const で宣言できるので、その場合、初期化の後でもそのオブジェクトのプロパティや配列の要素などは変更することができます。

const foo = {  //オブジェクト
  name: 'foo' 
};
console.log(foo.name);  //foo
     
foo.name = 'who';   //オブジェクトのプロパティ(name)を変更     
console.log(foo.name);  //who   

const bar = [1, 2, 3];  //配列
console.log(bar[1]);  //2(2番めの要素の値)
     
bar.forEach(function(value, index, array) { 
  array[index] = value * 2;  //配列の要素を変更(値を倍に)
});       
console.log(bar[1]);  //4
     
bar.shift();   //配列の要素を変更(先頭の要素を削除)   
console.log(bar[1]);  //6

MDN const

テンプレートリテラル

テンプレートリテラル(Template Literals)はシングルクォート( ')やダブルクォート(")の代わりにバッククォート(` )で文字列を囲むリテラルです。

テンプレートリテラルでは見た目通りに表示さ、改行もそのまま反映されます。スペースやタブ、インデントも反映されます。

let myStriing = `line 1
line 2
  line 3`;
console.log(myStriing); 

//以下のように出力されます
line 1
line 2
  line 3

テンプレートリテラルの中で ${ } を使って ${変数名} や ${式} のように記述すると ${ } の中の変数や式が展開されます。

let myString = "テスト";
// ${変数名} の例
console.log(`これは ${myString} です。`);  //これは テスト です。

function myBalance(x,y) {
  // ${式} の例
  console.log(`差額は${x-y}円です`);
}
myBalance(1000, 800);  //差額は200円です

以下のような場合、テンプレートリテラルを使うと簡潔に記述できます。

//テンプレートリテラル
function addLink(id, url, title, linkString) {
  let link = `<a href="${url}" title="${title}">${linkString}</a>`;
  document.getElementById(id).innerHTML = link;
}

//シングルクォートやダブルクォートを使った従来の文字列リテラル
function addLink(id, url, title, linkString) {
  let link = '<a href="' + url + '" title="' + title + '">' + linkString + '</a>';
  document.getElementById(id).innerHTML = link;
}

テンプレートリテラルでバッククォート(` )を表示するには、バックスラッシュ(\)を使ってエスケープする必要があります。

console.log(`バッククォート\`を表示`);  //バッククォート`を表示 

タグ付きテンプレートリテラル

タグ付きテンプレートリテラル(Tagged Template Literals)は、タグ(関数)とテンプレートリテラルを組み合わせた処理方法で以下のような書式で関数を呼び出します。

通常関数は呼び出す際の引数の指定に括弧 ( ) を使いますが、代わりにバッククォート ` ` を使い引数をテンプレート文字列として処理します。

関数`テンプレート`

上記のように記述すると、関数の第1引数にはテンプレートの内容を ${ } で区切った文字列の配列が渡され、第2引数以降には ${ } を展開した値が順番に渡されます。

以下は引数がどのように渡されるのかを確認する例です。

関数 myTag の呼び出しは myTag`<a href="${ url }">${ link }</a>${ target }`; のように引数にテンプレート文字列を指定します。

関数の呼び出しで指定した引数のテンプレート文字列はそれぞれの ${ } で分割されます。そして第1引数 strings には、分割された文字列を要素とする配列が渡されます。

${ } は3つあるので、文字列は4つに分割されます(この例の場合、4つ目の要素は空文字列になります)。

`文字列 ${ } 文字列 ${ } 文字列 ${ } 文字列 `

第2引数 url には ${ url }、第3引数 link には ${ link }、第4引数 target には ${ target } が渡されます。

function myTag(strings, url, link, target) {
  // 第1引数 strings は以下の4つ要素を持つ配列     
  let str0 = strings[0]; // <a href="
  let str1 = strings[1]; // ">
  let str2 = strings[2]; // </a>
  //let str3 = strings[3]; // この例の場合、最後の要素は空文字(使用しない)
  
  if(target) {
    return  `${str0}${url}" target="_blank" rel="noopener${str1}${link}${str2}`;
  }else{
    return  `${str0}${url}${str1}${link}${str2}`;
  }
}
        
let url = 'http://example.com';
let link = 'Example Doamain';   
let target = true;
        
let output = myTag`<a href="${ url }">${ link }</a>${ target }`;
console.log(output); 
//<a href="http://example.com" target="_blank" rel="noopener">Example Doamain</a>
// target = false の場合は以下のような出力
//<a href="http://example.com">Example Doamain</a>

raw プロパティ

タグ(関数)に渡される第1引数では、特別な raw プロパティが利用できます。raw プロパティにはエスケープ処理されていない「入力された通りの生(raw)の文字列」が配列として格納されています。

function myTag(strings) {
  console.log('strings.raw[0] :' + strings.raw[0]);  // raw プロパティ
  console.log('strings[0] :' + strings[0]); 
}
      
myTag`line 1 \n line 2`;

//以下が出力
strings.raw[0] :line 1 \n line 2  // \n は文字として出力される(raw プロパティ)
strings[0] :line 1    // \n は改行として処理される
 line 2
 
myTag`C:\Development\profile\about.html`;

//以下が出力
strings.raw[0] :C:\Development\profile\about.html   //入力された通り(raw プロパティ)
strings[0] :C:Developmentprofileabout.html   //バックスラッシュが取り除かれている

以下は関数の呼び出しのテンプレートの中で ${ } を使用しているので、第1引数 strings は 3つの配列の要素に分割され、raw プロパティも3つ生成されます。

${1} と ${2} はテンプレートを分割するために入れたもので、第2引数、第3引数として参照できますが使用していません。

function myTag(strings) {
  for(let i = 0 ; i < strings.length; i++) {
    console.log(strings.raw[i]); //配列の個々の要素の raw プロパティ
  }
  console.log(strings.raw); //配列全体(raw プロパティ)
}
      
myTag`line1 ${1} \n line2 ${2}`;

//以下が出力(配列の個々の要素と配列全体)
raw[0] : line1 
raw[1] :  \n line2 
raw[2] : 
["line1 ", " \n line2 ", ""] //3つの要素を持つ配列

String.raw() メソッド

また、生の文字列を生成するために、String.raw() というメソッドがあります。String.raw() はテンプレート文字列の生(raw)の文字列形式を取得するための関数です。

以下のようにすれば、テンプレートリテラル内のバックスラッシュはエスケープされず、そのまま出力されます。

const filePath = String.raw`C:\Development\profile\about.html`;
        
console.log(filePath); //C:\Development\profile\about.html

以下はテンプレートリテラルに ${ } を含めて値を展開(置換)する例です。

const file = '\\about.html';
const filePath = String.raw`C:\Development\profile${file}`;
        
console.log(filePath);  //C:\Development\profile\about.html

以下のように ${ } の前にバックスラッシュがあると置換されません。

const file = 'about.html';
const filePath = String.raw`C:\Development\profile\${file}`;
        
console.log(filePath);  //C:\Development\profile\${file}

MDN テンプレート文字列

引数

ES6(ECMAScript 2015)からデフォルト引数と Rest パラメータが使えるようになっています。

デフォルト引数

関数の引数は指定しなければ undefined になります。デフォルト引数は対応する引数が渡されていない場合や undefined が渡される場合にデフォルト値で値を設定することができます。

以下のように仮引数に対して「仮引数 = デフォルト値」という書式で、仮引数ごとにデフォルト値を指定することができます。

function 関数名( 仮引数 = デフォルト値, 仮引数 = デフォルト値 ) {

}

以下は割り算をする関数ですが、引数を省略した場合のデフォルトの値を指定しています。引数が指定されていなかったり、undefined が指定された場合はデフォルトの値が使われます。

function divide(x = 10, y = 5) {
  if(y !== 0) {
    return x / y;
  }else{
    return 'cannot divide by 0 or null';
  }
}
console.log( divide( 10, 2 ) );  //5
console.log( divide( 3 ) );   //0.6  
console.log( divide( 3, undefined ) );   //0.6 
console.log( divide() );  //2

以下のようにいずれかの引数のみにデフォルト値を指定することもできます。

function add(x, y = 5) {
  return x + y;
}
console.log( add( 10, 2 ) );  //12
console.log( add( 10 ) );  //15
console.log( add() );  //NaN
console.log( add( undefined, 3) );  //NaN

MDN デフォルト引数

Rest パラメータ

予め引数の個数が決まっていない関数(可変長引数の関数)を定義する際に、Rest パラメータ(残余引数)を使うことができます。

Rest パラメータは、関数の最後の引数に ... の接頭辞を付けることで残りの引数を配列として受け取ることができます。以下は個数が決まっていない引数を「...args」と記述して、配列 args で受け取る例です。

function funcRest(...args) {
  //args は配列
  for(let i = 0; i < args.length; i++) {
    console.log(args[i]);
  }
}
funcRest('a', 'b', 3, 'c');
//出力結果
a
b
3
c

配列は for...of 文が使えるので以下のように記述することができます。

function funcRest(...args) {
  for(let val of args) {
    console.log(val);
  }
}
funcRest('a', 'b');
//出力結果
a
b

他の引数と組み合わせる場合は、必ず Rest パラメータを末尾(最後)に指定する必要があります。

function funcRest(arg1, arg2, ...args) {
  console.log(arg1);
  console.log(arg2);
  console.log(args);
}
funcRest('a', 'b', 3, 'c');
//出力結果
a
b
[3, "c"]  //要素2つの配列

arguments オブジェクトは配列ではありませんが、Rest パラメータは配列なので sort、map、forEach、pop などの配列のメソッドを直接使用することができます。

function funcRest(...args) {
  let sortedArgs = args.sort();
  return sortedArgs;
}
console.log(funcRest( 'x', 'a', 'e', 'c'));

//出力結果
["a", "c", "e", "x"]

MDN 残余引数

スプレッド構文

スプレッド構文 ...(変数名の前にピリオド3つ)を使うと配列や文字列などの反復可能オブジェクト(iterable オブジェクト)を、記述した場所に展開(spread)することができます。

以下のようにスプレッド構文を記述した場所に値を展開することができます。

const fruits = ['apple', 'banana', 'orange'];
//スプレッド構文(変数名の前にピリオド3つ)で値を展開
console.log(...fruits);   
/* 出力結果
apple banana orange
*/

//配列を出力
console.log(fruits);
/* 出力結果
["apple", "banana", "orange"]
*/

//forEach を使って値を個々に取得
fruits.forEach(function(val) {
  console.log(val);
});               
/* 出力結果
apple 
banana 
orange
*/

以下は既存のオブジェクトをスプレッド構文を使って拡張する例です。

※オブジェクトでのスプレッド構文は ES2018 からの機能です。

const obj = {
  option1 : 'foo',
  option2 : 'bar'
};
      
const obj_ext = {
  ...obj,  //obj をここに展開
  option3 : 'boo'
  
};
      
console.log(obj);
// {option1: "foo", option2: "bar"}
   
console.log(obj_ext);
// {option1: "foo", option2: "bar", option3: "boo"}

以下はスプレッド構文を使って文字列を配列にする例です。

const word = 'JavaScript';
const word_array = [...word];  //配列に文字列の要素を展開

console.log(word_array);
// ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

関数呼び出しで配列を展開

配列の要素を引数にして関数を呼び出すには apply() を使って以下のように記述することができます。

function myFunc(x, y, z) {
  let sum = x + y + z;
  console.log('sum: ' + sum);
}
const args = [1, 2, 3];
myFunc.apply(null, args);  //sum: 6

//以下と同じ
myFunc(args[0], args[1], args[2]);  //sum: 6

スプレッド構文を使うと以下のように記述することができます。

Rest パラメータが引数をまとめて配列として関数に渡すのに対し、スプレッド構文は配列を展開して関数の引数に渡します。

function myFunc(x, y, z) {
  let sum = x + y + z;
  console.log('sum: ' + sum);
}
const args = [1, 2, 3];
myFunc(...args);   //sum: 6

//以下と同じ
myFunc(args[0], args[1], args[2]);  //sum: 6

また、スプレッド構文は引数の何番目でも使うことができ、複数回使うこともできます。

function myFunc(v, w, x, y, z) {
  let sum = v + w + x + y + z;
  console.log('sum: ' + sum);
}
const args = [1, 2, 3];
myFunc(4, ...args, 5);   //15

//以下と同じ
myFunc(4, args[0], args[1], args[2], 5);    //15

const args2 = [10, 20];
myFunc(...args, ...args2);  //36
      
//以下と同じ     
myFunc(args[0], args[1], args[2], args2[0], args2[1]);   //36

配列を複製

以下はスプレッド構文を使って配列を複製する例です。

let arr = [1, 2, 3];
let arr2 = [...arr];

console.log(arr2);  // [1, 2, 3]

配列を連結

スプレッド構文を使うと簡単に配列を連結したり結合することができます。

順序を入れ替えたり、要素を追加するなどが簡単に行なえます。

let arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
//arr1 に arr2 を連結
arr1 = [...arr1, ...arr2];
console.log(arr1);  // [1, 2, 3, 4, 5, 6]

//arr2 に要素を追加した配列を生成
const arr3 = [...arr2, 7, 8, 9];
console.log(arr3);   //[4, 5, 6, 7, 8, 9]

//arr2 に要素を追加した配列を生成
const arr4 = ['a', ...arr2, 'b'];
console.log(arr4);     // ["a", 4, 5, 6, "b"]

MDN スプレッド構文

分割代入

分割代入構文を使うと、配列やオブジェクトから値を取り出して複数の変数に代入することができます。

通常の代入と同じく代入演算子(=)を使いますが、左辺のオペランドが配列リテラルやオブジェクトリテラルとなります。

配列

以下は2つの変数(a と b)に配列 arr の要素の値(1 と 2)をそれぞれ代入する例です。

4行目で右辺の配列の値を取り出し、左辺の配列リテラルの対応する変数名へ代入しています。

const arr = [1, 2]; // この配列から値を取り出す
      
// 変数 a に arr[0] の値、変数 b に arr[1] の値を代入
const [a, b] = arr;

console.log(a); // 1
console.log(b); // 2

上記は以下と同じことです。

const arr = [1, 2];
const a = arr[0];
const b = arr[1];

通常の変数の代入と同様、変数の宣言と代入を分けて行うことができます。

let a, b;  //宣言と初期化
[a, b] = [1, 2];   //値の代入

console.log(a); // 1
console.log(b); // 2

対応する値がない場合の既定値を指定できます。対応する値がない場合は undefined になります。

let a, b;
//既定値を指定
[a = 3, b = 5] = [7];
      
console.log(a); // 7
console.log(b); // 5(既定値)

以下のように配列を返す関数の戻り値を代入することもできます。

function myFunc() {
  return [1, 2];
}

let [a, b] = myFunc(); 
console.log(a); // 1
console.log(b); // 2

Rest パターン ... を利用すると、個々の変数に割り当てられなかった残りの要素を1つの配列にまとめることができます。Rest パラメータと同様に必ず末尾の要素として指定する必要があります。

const [a, b, ...c] = [1, 2, 3, 4, 5];
 
console.log(a); // 1
console.log(b); // 2
console.log(c); // [3, 4, 5]

分割代入を利用すると、簡単に(一時変数を使わずに)変数の値を入れ替えることができます。

let a = 1;
let b = 2;

[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1

オブジェクト

オブジェクトも分割代入を利用することができます。 オブジェクトの場合、左辺は { } で囲み、右辺のオブジェクトのプロパティの値を左辺に対応する変数名(プロパティ名)へ代入します

以下はオブジェクト obj のプロパティの値を対応する変数(foo と bar)に代入する例です。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}

const { foo, bar } = obj;   
      
console.log(foo);  //FOO
console.log(bar);  //BAR

上記は以下と同じことです。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}

const foo = obj.foo;
const bar = obj.bar;

以下のように記述する代わりに、

const app = {
  name: 'foo',
  version: '1.0.0',
  type: 'apps'
} 
const name = app.name;
const version = app.version;
const type = app.type;

以下のように記述できるので簡潔になります。

const app = {
  name: 'foo',
  version: '1.0.0',
  type: 'apps'
} 

const { name, version, type } = app;

/* 以下と同じこと
const name = app.name;
const version = app.version;
const type = app.type;
*/

以下のような使い方もあります。

const {PI} = Math;
               
//const PI = Math.PI  と同じことですが、PI を2回記述せずに済みます        
          
console.log(PI);  //3.141592653589793

変数の並び順はプロパティの定義順と異なっていても問題ありません。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}
 
const { bar, foo } = obj;   
      
console.log(foo);  //FOO
console.log(bar);  //BAR

特定のプロパティのみを取得して代入することもできます。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}
 
const { bar } = obj;   
      
console.log(bar);  //BAR

対応する値がない場合の既定値を指定できます。対応する値がない場合は undefined になります。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}

//既定値を指定
const { foo = 'default1', bar = 'default2' , boo = 'default3'} = obj;   
      
console.log(foo);  //FOO
console.log(bar);  //BAR
console.log(boo);  //default3(既定値)

オブジェクトの分割代入で、変数の宣言と代入を分けて行う場合は、代入文の周りを括弧 ( ) で囲む必要があります。

代入文を括弧で囲まないとエラー(Uncaught SyntaxError: Unexpected token '=')になります。また、代入文の( )の式の前にセミコロンが必要です。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}
let foo, bar;  //セミコロンが必要

//代入文の周りを括弧で囲む  
({foo, bar} = obj);
      
console.log(foo);  //FOO
console.log(bar);  //BAR

プロパティと異なる変数名への代入

左辺の各変数を「プロパティ名 : 変数名」とすることで、プロパティと異なる名前の変数に値を割り当てることもできます。

以下は変数 a にプロパティ foo の値、変数 b にプロパテ bar の値を代入する例です。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}

//プロパティ名 : 変数名
const { foo:a, bar:b } = obj;      
      
console.log(a);  //FOO
console.log(b);  //BAR

「プロパティ名 : 変数名 = 既定値」とすることで、プロパティと異なる名前の変数に値を割り当てる場合でも既定値を指定できます。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}

const { foo:a= 'default1', bar:b= 'default2' , boo:c='default3' } = obj; 
   
console.log(a);  //FOO
console.log(b);  //BAR
console.log(c);  //default3(既定値) 

入れ子になったオブジェクト

入れ子になったオブジェクトは、オブジェクトとして変数に代入することができます。

以下は入れ子のオブジェクトをオブジェクトとして変数に格納する例です。

const obj = {
  foo: {
    name: 'Foo',
    age: 20
  },
  bar: {
    name: 'Bar',
    age: 30
  }
}

const { foo, bar } = obj; 
   
console.log(foo); //{name: "Foo", age: 20}
console.log(foo.age); //20
console.log(bar); //{name: "Bar", age: 30}
console.log(bar.age);  //30

また、プロパティ:{プロパティ名} という形で指定して値を取得することもできます。プロパティはこの場合オブジェクトなので、オブジェクト:{キー} とも言えます。

以下は プロパティ:{プロパティ名} という形で指定し入れ子になった値を取得する例です。

変数名は age とすると重複してしまうので、プロパティと異なる変数名(foo_age と bar_age)を「プロパティ名 : 変数名」で指定しています。

const obj = {
  foo: {
    name: 'Foo',
    age: 20
  },
  bar: {
    name: 'Bar',
    age: 30
  }
}

const {  foo: {age:foo_age}, bar: {age:bar_age}} = obj; 
   
console.log(foo_age); //20
console.log(bar_age); //30 

MDN 分割代入

引数

関数の引数にも分割代入が利用できます。

代入演算子(=)を使う場合は、左辺のオペランドがオブジェクトリテラルで、右辺にオブジェクトを指定しますが、引数の場合は、仮引数がオブジェクトリテラルで、関数に渡す引数にオブジェクトを指定します。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}        
function func({ foo }) {
    console.log(foo); 
}
func(obj); //FOO 

以下は入れ子になったオブジェクトの例です。

const obj = {
  foo: {
    name: 'Foo',
    age: 20
  },
  bar: {
    name: 'Bar',
    age: 30
  }
}       
function func({ foo: {name:foo_name}, foo: {age:foo_age}} ) { 
  console.log(foo_name + ' is ' + foo_age + ' years old.'); 
  //console.log(`${foo_name} is ${foo_age} years old.`); 
}
func(obj); //Foo is 20 years old.    

オブジェクトだけではなく配列も分割代入を利用することができます。

以下の例では、引数に渡された配列の1番目の要素が a に、2番目の要素が b に代入されます。

function func([a, b]) {
  console.log(a); // 1
  console.log(b); // 2
  let c = a + b;
  console.log(c);  // 3
}
const arr = [1, 2];
func(arr);

for...of 文

for...of 文を使うと反復可能オブジェクト(iterable オブジェクト)などに対して要素を1つずつ取り出して繰り返し処理を行うことができます。

以下が for...of 文の書式です。

for (変数 of 反復可能オブジェクト) {
   処理;
}

以下は for...of 文を使って配列の要素を出力する例です。

let fruits = ['apple', 'banana', 'orange'];
        
for (let fruit of fruits) {
  console.log(fruit);
}

/* 出力
apple
banana
orange
*/

以下のような HTML でクラスが foo の要素を getElementsByClassName() を使って取得すると、戻り値は HTMLCollection です。

<body>
  <p class="foo">Foo 1</p>
  <div class="foo">Foo 2</div>
  <p><span class="foo">Foo 3</span></p>
</body>

HTMLCollection は反復可能オブジェクトなので、for...of 文を使うことができます。

let foo = document.getElementsByClassName('foo');
  
for (let elem of foo) {
  console.log(elem.tagName + ': ' + elem.textContent);
}

/* 出力
P: Foo 1
DIV: Foo 2
SPAN: Foo 3
*/

JavaScript では String オブジェクトも反復可能オブジェクトなので、文字列を1文字ずつ列挙することができます。

const str = 'Java';
  
for (let char of str) {
  console.log(char);
}

/* 出力
J
a
v
a
*/

オブジェクトが「反復可能オブジェクト」でない場合、for...of 文を使用するとエラーになります。

const obj = {
  foo: 'FOO',
  bar: 'BAR'
}
  
for (let prop of obj) { //エラー Uncaught TypeError: obj is not iterable
  console.log(prop);
}

MDN for...of

反復可能オブジェクト(iterable)

反復可能オブジェクト(iterable object)とはイテレータ(iterator)を持つオブジェクトのことで、イテレータによりオブジェクトの要素(プロパティ)を順番に取り出すことができるオブジェクトです。

イテレータはデータ構造に対して反復処理によって各要素を返す機能を持つオブジェクトです。

反復可能オブジェクトは Symbol.iterator をキーとするメソッド(@@iterator)を持ち、そのメソッドを実行するとイテレータを返す仕組みになっています。

大雑把に言うと、反復可能オブジェクトとは反復処理(繰り返し処理)時に各要素を返す動作が定義されたオブジェクトになります。

組み込み型(ビルトイン)オブジェクトでは配列や文字列、MapSet などが該当します。

反復可能オブジェクトと配列のようなオブジェクト

「反復可能オブジェクト(iterable object)」に似たような「配列のようなオブジェクト(array-like object)」がありますが、この2つは異なるものです。

オブジェクト 説明
反復可能オブジェクト Symbol.iterator メソッドを実装したオブジェクト
配列のようなオブジェクト length(長さ)とインデックスでアクセスできる要素を持つオブジェクト

文字列のようにオブジェクトによっては両方を兼ね備えるオブジェクトもあります。

但し、「反復可能オブジェクト」が「配列のようなオブジェクト」であるとは限りませんし、逆に「配列のようなオブジェクト」が「反復可能オブジェクト」とも限りません。

共通点

「反復可能オブジェクト」と「配列のようなオブジェクト」の共通点は、両方とも配列ではないということです。そのためこれらのオブジェクトは配列のメソッドを直接使用することはできません。

Array.from() メソッド

「反復可能オブジェクト」や「配列のようなオブジェクト」で配列のメソッドを使うには、Array.from() メソッドを使ってそれらを配列に変換してから配列のメソッドを使う方法があります。

以下は querySelectorAll() を使ってクラス foo の要素の集合(NodeList)を取得する例です。戻り値は NodeList オブジェクトで、このオブジェクトは「反復可能オブジェクト」であり、また「配列のようなオブジェクト」でもあります。

let elems = document.querySelectorAll('.foo');  //NodeList
console.log(elems);

//出力例
NodeList(2) [p.foo, p.foo]
0: p.foo
1: p.foo
length: 2
__proto__: NodeList

Array.from() メソッドを使うと新たに配列を生成することができます。

let elems_array = Array.from(document.querySelectorAll('.foo'));   //Array
console.log(elems_array);

//出力例
(2)[p.foo, p.foo]
0: p.foo
1: p.foo
length: 2
__proto__: Array(0)

以下は Chrome のインスペクタでの表示です。__proto__ プロパティを展開するとそれぞれの持つプロパティやメソッド、 Symbol(Symbol.iterator) などが確認できます。

イテレータ(反復子)

反復可能オブジェクト(iterable object)は Symbol.iterator というシンボルをキーとして参照できるメソッドを持ち、そのメソッド [Symbol.iterator]() を実行するとイテレータを返します。

イテレータ(Iterator)はデータ構造(コレクション)に対して繰り返し処理を行って各要素を返す仕組みを持つオブジェクトです。

また、イテレータは next() というメソッドを実装していて、value と done という2つのプロパティを持つ IteratorResult というオブジェクトを返します。

以下は反復可能オブジェクトの1つ配列のイテレータの例です。

配列(array)を生成し、そのメソッド [Symbol.iterator]() を実行してイテレータを取得して、イテレータのメソッド next() を実行してブラウザのインスペクタで確認しています。

ブラウザのインスペクタで表示される __proto__ を展開するとメソッド next() を持っていることが確認できます。

また、next() の実行結果のオブジェクト(IteratorResult)は、value と done プロパティを持っていることを確認できます。

//配列を生成
let array  = ['a', 'b', 'c'];

//メソッド [Symbol.iterator]() を実行してイテレータを取得
let iterator = array[Symbol.iterator]();

//イテレータを出力
console.log(iterator);  //Array Iterator {}

/* 以下はイテレータ(Array Iterator)をインスペクタで展開した内容 */
Array Iterator {}
  __proto__: Array Iterator
    next: ƒ next()   // next() メソッドを持っている
      Symbol(Symbol.toStringTag): "Array Iterator"
    __proto__:
      Symbol(Symbol.iterator): ƒ [Symbol.iterator]() //Symbol.iterator
        arguments: (...)
        caller: (...)
        length: 0
        name: "[Symbol.iterator]"
      __proto__: ƒ ()
      [[Scopes]]: Scopes[0]
    __proto__: Object
/* ここまで(イテレータをインスペクタで展開した内容) */

//イテレータ の next() メソッドを実行して結果(IteratorResult)を取得
let iteraotResult = iterator.next();

//結果(IteratorResult)を出力
console.log( iteraotResult ); 

//出力:value と done プロパティを持つオブジェクト
{value: "a", done: false}

以下は配列(反復可能オブジェクト)のイテレータを取得して while 文で next() メソッドの実行結果(IteratorResult)のプロパティ value と done をテンプレートリテラルを使って出力する例です。

done プロパティの値は次の要素がある間は false で、次の要素がなくなると true に変わるのでその時点で終了しています。

value プロパティの値は次の要素がある間はその要素の値が表示され、次の要素がなくなると undefined になります。

let array  = ['a', 'b', 'c'];
//配列のイテレータ
let iterator = array[Symbol.iterator]();

//next() メソッドを反復処理して結果を表示
while(true){
  // iteratorResult を取得
  let iteratorResult = iterator.next(); 
  //value プロパティと done プロパティの値をコンソールに出力
  console.log(`value: ${iteratorResult.value}, done: ${iteratorResult.done}`); 
  //done プロパティの値が true になったら終了(break)
  if(iteratorResult.done) break; 
}    

//出力
value: a, done: false
value: b, done: false
value: c, done: false
value: undefined, done: true
Symbol.iterator の例

以下は独自の Symbol.iterator メソッドを実装してオブジェクトを反復可能(iterable)にする例です(機能を確認するためのもので実用的なものではありません)。

反復可能オブジェクトは Symbol.iterator というシンボルをキーとするメソッド [Symbol.iterator]() を持たなければなりません。

このメソッドはイテレータを返し、イテレータは next() というメソッドを実装し、以下のような value と done という2つのプロパティを持つ IteratorResult というオブジェクトを返します。

next() メソッドが返すオブジェクト IteratorResult のプロパティ
プロパティ 説明
done 真偽値。次の値を生成できるときは false で反復シーケンスを終了した場合は true になります。
value 任意の JavaScript 値。done が true のときは省略可能。

以下の例ではオブジェクトの要素(プロパティ)の数は Object.keys() で取得した配列の length を利用しています。

next() メソッドでは要素数以下の場合は done に false を設定し、要素数より大きい場合は true を設定しています。

また、value には this[keys[current]] でプロパティの値を表示するようにしています(アロー関数を使用しているので this はこのオブジェクト obj になります)。

//オブジェクトを作成
var obj = {foo: 'FOO', bar: 'BAR', boo: 'BOO'}; 
// オブジェクトを iterable に(Symbol.iterator を実装)
obj[Symbol.iterator] = function(){
  // イテレータオブジェクト
  var iterator = {}; 
  // カウンタ(現在の要素番号)
  var current = 0;
  // このオブジェクト(this → obj)のプロパティのキーの配列
  var keys = Object.keys(this);
  //イテレータの next() メソッド
  iterator.next = ()  => {
    // IteratorResult: value と done プロパティの定義
    var iteratorResult = (current < keys.length) 
      //current が要素数以下の場合(done は false を指定)
      ? { value: this[keys[current]], done: false }
      //current が要素数より大きい場合(done は true を指定。value は省略可能)
      : { value: undefined, done: true };
    current++;
    //next() は IteratorResult を返す
    return iteratorResult; 
  };
  // メソッドはイテレータを返す
  return iterator;
};

イテレータを実装すると、スプレッド構文for...of 文が使えます。

var obj = {foo: 'FOO', bar: 'BAR', boo: 'BOO'}; 
//イテレータを実装
obj[Symbol.iterator] = function(){
  var iterator = {}; 
  var current = 0;
  var keys = Object.keys(this);
  iterator.next = ()  => {
    var iteratorResult = (current < keys.length) 
      ? { value: this[keys[current]], done: false }
      : { value: undefined, done: true };
    current++;
    return iteratorResult; 
  };
  return iterator;
};
          
//スプレッド構文
console.log(...obj);   //FOO BAR BOO

//for...of 文
for(var value of obj) {
  console.log(value);
}
/*
FOO
BAR
BOO
*/

イテレータを持っていない場合は、以下のようなエラーになります。

var obj = {foo: 'FOO', bar: 'BAR', boo: 'BOO'}; 
// スプレッド構文        
console.log(...obj);  
//エラー Uncaught TypeError: Found non-callable @@iterator  

//for...of 文
for(var value of obj) {
  console.log(value);
}
//エラー Uncaught TypeError: obj is not iterable

参考サイト

配列

ES6(ECMAScript 2015)ではスプレッド構文分割代入以外に、以下のような配列のメソッドが追加されています。

Array.from()

Array.from() メソッドは、配列のようなオブジェクト反復可能オブジェクトから新たに配列を生成します。

以下が書式です。

Array.from(arrayLike, mapFn, thisArg)
引数 説明
arrayLike 配列に変換する「配列のようなオブジェクト」や「反復可能オブジェクト」
mapFn (オプション)配列に追加する前に各要素に適用する Map 関数。この関数には以下の引数が与えられます。
  • value:現在処理中の要素の値
  • index:現在処理中の要素の配列内におけるインデックス
thisArg (オプション)mapFn を実行する時に this として使用する値を指定。

以下は文字列から配列を生成する例です。

let foo = Array.from('foo'); //文字列から配列を生成      
console.log(foo);  //["f", "o", "o"]

//Map 関数で大文字に変換
let foo_capital = Array.from('foo', function(char) {
  return char.toUpperCase();
});   
console.log(foo_capital);  // ["F", "O", "O"]

//Map 関数で各文字にインデックス番号を追加
let foo_index = Array.from('foo', function(char, index) {
  return index + ':' + char;
});     
console.log(foo_index); // ["0:f", "1:o", "2:o"] 

以下は getElementsByTagName() で取得した配列のようなオブジェクト(HTMLCollection)を配列に変換して、配列のメソッド forEach() を使う例です。

Array.from(document.getElementsByTagName('div')).forEach(function(elem) {
  if(elem.id) {
    //取得した div 要素に id が付与されていれば出力
    console.log(elem.id);
  }
});

//出力例
/*
header
header_inner
search_box
navi
container
content
・・・*/

MDN Array.from()

Array.of()

Array.of() メソッドは、可変長の引数を受け取り新しい配列を生成します。

以下が書式です。

Array.of(element0, element1, ..., elementN)

Array コンストラクタを使って配列を生成するのと異なるのは、引数が1つで数値の場合です。

Array.of(3) は値が3の単一の要素を持つ配列を作成しますが、Array(3) は length プロパティが3の空の配列を作成します。

let arrOf3 = Array.of(3);     
console.log(arrOf3);    
/*
[3]  単一の要素を持つ配列
0: 3  
length: 1
 */   
 
let arr3 = Array(3);  //Array コンストラクタ(new を省略)
console.log(arr3);  
/*
(3) [empty × 3] length プロパティが3の空の配列
length: 3  
*/

//以下の場合は同じ
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
console.log(Array(1, 2, 3));   // [1, 2, 3]

console.log(Array.of('a', 'b', 'c')); // ["a", "b", "c"]
console.log(Array('a', 'b', 'c'));  // ["a", "b", "c"]

また、Array コンストラクタの場合、数値で 0 または正の整数でない場合はエラーになりますが、Array.of() は指定した数値が値の要素を作成します。

console.log(Array.of(-1)); //[-1]   
      
console.log(Array(-1));  // Uncaught RangeError: Invalid array length
      
console.log(Array.of(5.7));  //[5.7]       

console.log(Array(5.7));  //Uncaught RangeError: Invalid array length

MDN Array.of()

アロー関数

アロー関数は関数リテラル(関数式)を簡潔に記述する代替構文です。

アロー関数には以下のような特徴があります。

  • 簡潔に記述できる
  • this を束縛しない(アロー関数自身は this を持たない)
  • コンストラクタとして使うことはできない(new できない)
  • arguments を参照できない

以下は同じ関数を従来の関数リテラルとアロー関数で記述する例です。

//関数リテラル
const multiply = function(x, y) {
  return x * y;
}
console.log(multiply(3,6));  // 18
 
//アロー関数
const multiply  = (x, y) => {
  return x * y;
};
console.log(multiply(3,6));  // 18

アロー関数を使うと「function」を記述する必要がなく関数をより短く記述することができます。

//関数リテラル
const 関数名 = function(引数) {
  // 関数の処理
  return 関数の返す値;
}

//アロー関数
const 関数名 = (引数) => {
    // 関数の処理
    return 関数の返す値;
};

条件によっては更に簡潔に記述することができます。

条件 記述
処理が1行のみの場合 関数の処理が1行のみの場合ブロック { } と return を省略可能
const multiply  = (x, y) => { return x * y; };

//以下のように記述できます。
const multiply  = (x, y) => x * y;
引数が1つの場合 ( ) を省略可能
const square = (x) => { return x * x };
      
//( ) を省略して以下のように記述できます。
const square = x => { return x * x };
      
//処理が1行のみなので以下のように記述できます。
const square = x => x * x;
引数がない場合 ( ) は省略できない
const hello = ()  => { console.log("Hello Arrow!"); } 

//処理が1行のみなのでブロック { } は省略可能。
const hello = () => console.log("Hello Arrow!");

hello();  // Hello Arrow!

アロー関数は簡潔に記述することができるため、コールバック関数などでコードの見通しが良くなります。

例えば、配列のメソッド map() は配列の各要素に対してコールバック関数を呼び出し、結果を含む配列を返します。関数リテラルを使って記述すると以下のようになります。

const memebers = ['foo', 'bar', 'boo'];
      
//各要素に対してコールバック関数で処理(関数リテラル)
const capitalized = memebers.map(function(value) {
    return value.toUpperCase(); // 返した値の配列が生成される
});
      
console.log(capitalized); //  ["FOO", "BAR", "BOO"]  

アロー関数を使えば以下のように記述することができます。

4行目の value => value.toUpperCase() の最後にセミコロンを付けるとエラーになります。

const memebers = ['foo', 'bar', 'boo'];
      
//各要素に対してコールバック関数で処理(アロー関数)
const capitalized = memebers.map( value => value.toUpperCase() );
      
console.log(capitalized); //  ["FOO", "BAR", "BOO"] 

this

this の参照先は、アロー関数とそれ以外の関数定義で異なります。

アロー関数は this を持たない

アロー関数自身は this を持っていません(内部で this が定義されていません)。もし this がアクセスされた場合は、外側のスコープ(関数)を探索します。

そのため、アロー関数における this は「アロー関数自身の外側のスコープに定義された最も近い関数の this の値」になります。

以下のようなメソッド内におけるコールバック関数でアロー関数は便利です。

forEach のコールバック関数ではアロー関数が使われているので、その中の this.name の this は showObj() の this の値(obj.name)になります。

let obj = {
  name: 'Three brothers',
  members: ['foo', 'bar', 'boo'],
  showObj() {
    this.members.forEach(
      //以下の this.name は obj.name と同じになる
      member => console.log(this.name + ': ' + member)
    );
  }
};
obj.showObj();

/* 出力
Three brothers: foo
Three brothers: bar
Three brothers: boo
*/ 

もし 通常の関数リテラル(function)を使った場合、コールバック関数の外側のスコープで this を一時変数に代入しないと、エラーになってしまいます。

let obj = {
  name: 'Three brothers',
  members: ['foo', 'bar', 'boo'],
  showObj() {
    //コールバック関数の外側のスコープで this を一時変数に代入
    let that = this;
    this.members.forEach(function(member) {
      //that でアクセス
      console.log(that.name + ': ' + member)
    });
  }
};


//以下の場合は undefined.name にアクセスするためエラーになります
let obj = {
  name: 'Three brothers',
  members: ['foo', 'bar', 'boo'],
  showObj() {
    this.members.forEach(function(member) {
      console.log(this.name + ': ' + member)  //エラー
      //Uncaught TypeError: Cannot read property 'name' of undefined
    });
  }
};

以下は Symbol.iterator のメソッドを設定する例で、最初の例は通常の関数リテラル(function)とアロー関数を使っていますが、それを関数リテラルのみで記述する場合とアロー関数のみで記述する場合の例です。

var obj = {foo: 'FOO', bar: 'BAR', boo: 'BOO'}; 

//obj にイテレータを実装
obj[Symbol.iterator] = function() { //関数リテラル
  var iterator = {}; 
  var current = 0;
  var keys = Object.keys(this);  //this は obj
  iterator.next = ()  => {  //アロー関数
    var iteratorResult = (current < keys.length) 
      ? { value: this[keys[current]], done: false }  //this は obj
      : { value: undefined, done: true };
    current++;
    return iteratorResult; 
  };
  return iterator;
}; 

//8行目からのアロー関数を function() に書き換え
obj[Symbol.iterator] = function() { //関数リテラル
  var iterator = {}; 
  var current = 0;
  var keys = Object.keys(this);
  var that = this;  // this を一時変数 that に代入
  iterator.next = function()  { //関数リテラル
    var iteratorResult = (current < keys.length) 
      ? { value: that[keys[current]], done: false }  //一時変数 that
      : { value: undefined, done: true };
    current++;
    return iteratorResult; 
  };
  return iterator;
}; 

//両方ともアロー関数に書き換え
obj[Symbol.iterator] = () =>{ //アロー関数
  var iterator = {}; 
  var current = 0;
  var keys = Object.keys(obj);  //this は Window
  iterator.next = ()  => { //アロー関数
    var iteratorResult = (current < keys.length) 
      ? { value: obj[keys[current]], done: false }  //this は Window
      : { value: undefined, done: true };
    current++;
    return iteratorResult; 
  };
  return iterator;
};

イベントハンドラ

以下はボタン <button id="btn">Click</button> をクリックすると、そのボタンのテキスト(Click)を表示するイベントハンドラの例です。

//イベントを登録する要素を取得
var btn = document.getElementById("btn");

//要素にイベントリスナーを登録
btn.addEventListener('click',function() {
  //this は btn(クリックされるボタン要素)
  alert(this.textContent);  
}); 

アロー関数を使って以下のように記述すると、ボタンをクリックした際には undefined と表示されてしまいます。

btn.addEventListener('click', () => alert(this.textContent) );

以下のように引数にイベント(e)を渡して this の代わりにイベントのプロパティ(e.currentTarget 現在のターゲットへの参照)を指定すると期待通りに動作します。

btn.addEventListener('click', (e) => alert(e.currentTarget.textContent) ); 

jQuery のコールバック

jQuery のコールバックにアロー関数を利用する場合の this も注意する必要があります。

以下は jQuery の each() を使って、全ての h3 要素のテキストを表示する例です。

jQuery(function($){
  $('h3').each( function() {
    console.log($(this).text()); // 全ての h3 要素のテキストが表示される
  });
});

アロー関数を使って以下のように記述すると、想定外の結果になります。

jQuery(function($){
  $('h3').each( () => {
    console.log($(this).text());  //想定外の表示になる
  });
});

each() の場合、第1引数には各要素のインデックス番号が、第2引数には繰り返し処理中の要素が渡されるので、この場合は第2引数を利用すると機能します。

jQuery(function($){
  $('h3').each( (index, element) => {
    console.log($(element).text()); // 全ての h3 要素のテキストが表示される
  });
});

JavaScript Primer 関数とthis

MDN アロー関数

モジュール

ES6(ECMAScript 2015)では JavaScript プログラムをモジュールに分割して管理する仕組みが導入されました。

caniuse JavaScript modules via script tag

モジュールは単なる1つのファイルで、1つの JavaScript ファイルに対応します。

export 文を使用することで変数や関数などをエクスポートすることができ、エクスポートしたモジュールは import 文を使用して利用することができます(export / import)。

読み込まれる側(モジュール側)

以下はモジュールとしての外部 JavaScript ファイル(module_01.js)の例です。

モジュールの JavaScript ファイルでは export 文を使ってアクセス可能な変数や関数などを定義します。

export で定義されたり、宣言されたものだけが他の JavaScript ファイルから参照可能になります。

module_01.js
//export で関数 showMessage を定義
export function showMessage(message) {
  console.log(message);
}

読み込む側

以下は HTML ファイルでモジュールを import を使って読み込む例です。

<script> 要素に type="module" を指定する必要があります。

import の from でモジュールのファイルを指定します。その歳、パスを指定する必要があります。この例の場合は HTML とモジュールは同じ階層にあるので「./」と指定しています。

<!-- HTML で script タグで読み込み-->
<script type="module">
// import で関数 showMessage を module_01.js からインポート
import {showMessage} from "./module_01.js";
// インポートした関数を実行  
showMessage('Hello Module!');
</script>

src 属性で外部ファイルを指定することもできます。

例えば、以下のような外部ファイル(imports.js)を作成し、HTML ファイルで読み込むことができます。

imports.js
// import で関数 showMessage を module_01.js からインポート
import {showMessage} from "./module_01.js";
// インポートした関数を実行    
showMessage('Hello Module!');

HTML ファイルで以下のように src 属性を指定して読み込みます。※ この場合も type="module" を指定する必要があります。

<!-- HTML で script タグで src 属性を使って外部ファイルを読み込み-->
<script src="imports.js" type="module"></script>

※ import 文と export 文は、モジュールの中でのみ使うことができます。通常のスクリプトの中では使えません。

モジュールの特徴

モジュールとして読み込まれるソースコードは以下のような特徴があります。

  • 常に Strict mode として動作します(未宣言の変数への代入はエラーになります)
  • 各モジュールには独自のトップレベルのスコープがあります
  • トップレベルでの変数はグローバルではなくモジュール内のローカル変数として定義されます
  • トップレベルの this は window ではなく undefined になります
  • モジュールスクリプトは defer 属性と同じ効果を持ちます

また、モジュールコードはインポート時の初回にのみ評価され、同じ src の外部スクリプトは一度だけ実行されます。

以下の場合、imports.js は一度だけ取得され実行されます。

<script src="imports.js" type="module"></script> 
<script src="imports.js" type="module"></script> 

export / import

モジュールの機能にアクセスするできるようにするには、export 文を使ってその機能をエクスポートする必要があります。そしてエクスポートしたモジュールは import 文を使用して利用することができます

エクスポートできるものは、関数、変数(var、let、const)及びクラスです。

また、エクスポートとインポートの方法は「名前付き」と「デフォルト」の2種類があります。

種類 説明
名前付きエクスポート モジュールごとに複数のエクスポートを行うエクスポート
名前付きインポート モジュールから名前を指定して選択的に行うインポート
デフォルトエクスポート モジュールごとに1つしかエクスポートできないエクスポート
デフォルトインポート モジュールのデフォルトエクスポートに名前をつけて行うインポート
名前付き

名前付きエクスポート(named export)

名前付きエクスポートは、モジュールごとに複数の機能(変数や関数、クラスなど)をエクスポートすることができます。

変数や関数、クラスの宣言の前に export を記述することで宣言と同時にそれぞれの機能を個別にエクスポートできます。

宣言と同時に名前付きエクスポート
//変数をエクスポート
export const foo = 'foo';
//配列をエクスポート
export let bar = [1, 2, 3];
//関数をエクスポート
export function myFunc(val) {
  console.log(val);
}
//関数をエクスポート(アロー関数)
export const hello = () => console.log("Hello");

宣言とは別にエクスポートすることもできます。宣言済みの機能を以下のような書式でモジュールファイルの末尾に単一の export 文を使ってエクスポートすることもできます。

export { 機能のカンマ区切りリスト };

以下は前述の「宣言と同時に名前付きエクスポート」したのと同じ結果になります。

宣言とは別に名前付きエクスポート
const foo = 'foo';  //変数の宣言
let bar = [1, 2, 3];  //配列の宣言
function myFunc(val) {  //関数の宣言
  console.log(val);  
}
const hello = () => console.log("Hello");  //関数の宣言

//宣言済みの機能を export 文でリストで指定してエクスポート
export { foo, bar, myFunc, hello };

名前付きインポート(named import)

名前付きインポートはエクスポートされた機能の名前を指定してモジュールから選択的にインポートすることができます。

以下のように import 文に続けてインポートしたい機能のリストをカンマ区切りで { }内に指定し、from でモジュールファイルへのパスを指定します。

モジュールファイルへのパスは、サイトのルートからの(相対)パスを指定します。「現在の位置」を意味するドット (.) 記法を使うと簡潔に記述でき、可搬性もあります。

import { 機能のカンマ区切りリスト } from 'モジュールのパス';

以下は imports.js というファイルで、モジュール module_01.js から機能の名前を指定してインポートする例です。

imports.js
//名前を指定してインポート
import {foo, bar, myFunc} from './module_01.js';
//インポートした関数にインポートした変数を指定して実行
myFunc(foo);  //foo
myFunc(bar);  //[1, 2, 3]

上記のファイルを HTML ファイルで読み込むには src 属性に imports.js を指定し、type="module" を指定する必要があります。

<script src="imports.js" type="module"></script>

以下は直接 HTML ファイルにモジュール(module_01.js)から myFunc のみをインポートする例です。

<script type="module">
import { myFunc } from './module_01.js';
myFunc('Hello!');  //インポートした関数を実行
</script>

全てのエクスポートをまとめてインポートすることもできます(モジュールオブジェクトの作成)。

デフォルト

デフォルトエクスポート(default export)

デフォルトエクスポートは、モジュール毎に1つしかエクスポートできないエクスポートで、モジュールがデフォルトの機能(モジュール毎に1つの機能)を持つことができるように設計されたものです。

メイン(デフォルト)の機能の宣言の前に export default を記述することで宣言と同時にその機能をデフォルトとしてエクスポートできます(デフォルトエクスポートは 1 つに限ります)。

//宣言と同時に関数 myFunc をデフォルトエクスポート
export default function myFunc(val) {
  console.log(val);
}

宣言と同時にデフォルトエクスポートする場合、関数やクラスの名前を省略することができます。

//宣言と同時に名前を省略して関数をデフォルトエクスポート
export default function(val) {
  console.log(val);
}
//宣言と同時にアロー関数をデフォルトエクスポート
export default () => console.log("Hello");

また、変数の作成なしで単一値(名前のない値)をエデフォルトエクスポートすることができます。

export default [1, 2, 3];

変数の場合は、宣言とデフォルトエクスポートを同時に行うことはできません。以下はエラーになります。

export default const foo = 'foo';  //エラー 
//Uncaught SyntaxError: Unexpected token 'const'

//以下のように変数を作成せずに値のみをデフォルトエクスポートすることはできます
export default 'foo';

宣言とは別に、すでに宣言されている変数や関数をデフォルトエクスポートすることもできます。

デフォルトとしてエクスポートしたい機能を export default 文で指定します。{ } は付けません。

以下はすでに宣言されている変数をデフォルトエクスポートする例です。

const foo = 'foo';
//宣言済みの変数をデフォルトエクスポート
export default foo;

以下はすでに宣言されている関数をデフォルトエクスポートする例です。

function myFunc(val) {
  console.log(val);
}
//宣言済みの関数をデフォルトエクスポート
export default myFunc;
const hello = () => console.log("Hello");

//宣言済みの関数をデフォルトエクスポート
export default hello;

デフォルトインポート(default import)

デフォルトインポートは、モジュールのデフォルトエクスポートに名前をつけてインポートします。

以下のように import 文に続けて名前を指定し、from でモジュールファイルへのパスを指定します。

指定する名前は任意の名前を付けることができます。デフォルトエクスポートはモジュールごとに1つしか作れないため、インポートする機能が何かはわかっています。

import 名前 from 'モジュールのパス';

デフォルトエクスポート同様、名前には { } は付けません。上記の書式は以下の簡略表現です。

import {default as 名前} from 'モジュールのパス';

以下は imports_02.js というファイルで、モジュール module_02.js のデフォルトエクスポートをインポートする例です。

module_02.js
//デフォルトエクスポート
export default function myFunc(val) {
  console.log(val);
}
imports_02.js
//デフォルトインポート
import myFunc from './module_02.js';
//インポートした関数を実行
myFunc('bar');

上記の例では、インポートする際の名前をデフォルトエクスポートの関数名と同じにしていますが、以下のように任意の名前を指定できます。

imports_02.js
import foo from './module_02.js';
//指定した名前でインポートした関数を呼び出す
foo('bar');
//myFunc() が実行される
エイリアス

import 文や export 文の { } の中で、キーワード as と新しい名前(別名/エイリアス)を指定することで、その機能を使うときの名前を変更することができます。

異なる名前でエクスポート

エイリアスを使うと、宣言済みの変数や関数などを異なる名前でエクスポートできます。以下のように as の後にエクスポートしたい名前(エイリアス)を指定します。

module_03.js
const foo = 'foo';  //変数の宣言
let bar = [1, 2, 3];  //配列の宣言
function myFunc(val) {  //関数の宣言
  console.log(val);  
}
 
//as と新しい名前を指定してエクスポート
export { foo as f, bar as b, myFunc as mf };

インポートする側では、新しい名前(エイリアス)を指定します。

import {f, b, mf} from './module_03.js';

mf(f);  //foo
mf(b);  //[1, 2, 3]

異なる名前でインポート

異なる名前でインポートすることもできます。インポートでも同様に、as の後に別名(エイリアス)を指定します。

module_04.js
const foo = 'foo';  //変数の宣言
let bar = [1, 2, 3];  //配列の宣言
function myFunc(val) {  //関数の宣言
  console.log(val);  
}
 
//名前付きエクスポート
export { foo, bar, myFunc };
//別名を指定してインポート
import {foo as f, bar as b, myFunc as mf} from './module_04.js';
//別名を使って実行
mf(f);  //foo
mf(b);  //[1, 2, 3]
モジュールオブジェクトの作成

以下の構文を使うことで、全てのエクスポートをまとめてモジュールオブジェクトとしてインポートすることができます。これは、モジュールの中にある全てのエクスポートを取得して、それらをオブジェクトのメンバーとして利用できるようにします。

import * as オブジェクト from 'モジュールのパス';

また、オブジェクトのメンバーとして利用できるようにすることで独自の名前空間を持たせるような効果があります(名前の衝突を回避できます)。

以下はモジュール module_05.js の全てのエクスポートを取得する例です。

module_05.js
export const foo = 'FOO';  //名前付きエクスポート
export let bar = [1, 2, 3];  //名前付きエクスポート
export function myFunc(val) {  //名前付きエクスポート
  console.log(val);  
}
export default ['a', 'b', 'c']; //デフォルトエクスポート
//全てのエクスポートを myModule オブジェクトとしてインポート
import * as myModule from './module_05.js';

// myModule オブジェクトのメンバーとしてアクセス
myModule.myFunc(myModule.foo); //FOO
myModule.myFunc(myModule.bar); //[1, 2, 3]
//デフォルトエクスポートにもアクセス可能
console.log(myModule.default); //['a', 'b', 'c']
名前の衝突の回避

例えば、以下のように同じ名前を持つ関数を異なるモジュールからインポートすると名前の衝突によるエラーが発生します。

module_x.js
const foo = 'foo';
let bar = [1, 2, 3];
function myFunc(val) {
  console.log('x:' + val);
}
export { foo, bar, myFunc };
module_y.js
const foo = 'FOO';
let bar = [100, 200, 300];
function myFunc(val) {
  console.log('y:' + val);
}
export { foo, bar, myFunc };
// 同じ名前の関数をインポート
import { myFunc } from './module_x.js';
import { myFunc } from './module_y.js';
//エラー Uncaught SyntaxError: Identifier 'xxxx' has already been declared

名前の衝突によるエラーを回避するにはインポートする際に、エイリアスを使ったり、モジュールオブジェクトを作成するなどの方法があります。

エイリアスを使う

以下のように同じ名前の機能を異なる名前でインポートすればエラーを回避できます。

import { myFunc as funcX } from './module_x.js';
import { myFunc as funcY } from './module_y.js';
  
funcX('Hello!');  //x:Hello!
funcY('Hello!');  //y:Hello!

モジュールオブジェクトを作成

以下は全てのエクスポートをまとめてモジュールオブジェクトとしてインポートすることで、名前の衝突によるエラーを回避する例です。

// 同じ名前の関数を異なる名前でインポート
import * as modX from './module_x.js';
import * as modY from './module_y.js';
  
modX.myFunc(modX.foo);  //x:foo
modY.myFunc(modY.foo);  //y:FOO

CommonJS の require

以下は JavaScript ES6 のモジュールとは直接関係はありません。

Node.js を使っている場合は import 文に似た require() 関数が使えます(JavaScript には require という関数や文はありません)。

require() は CommonJS と呼ばれるモジュールシステムのモジュールをインポートするための関数(構文)で Node.js で使うことができます。(関連ページ:Node.js の exports と module.exports

以下は ES6(ES Modules)の export と import を使う例です。

module.js
//test という関数をエクスポートするファイル(モジュール)
export const test = function() {
  console.log('ログ出力');
}
test.html
//test という関数をインポートして使用するファイル test.html
<script type="module">
  import { test } from './module.js'
  test();  //ログ出力(コンソールに出力される)
</script>

以下は同様なことを require を使って行う例です(Node.js がインストールされている必要があります)。

node_module.js(モジュール側)
//module.exports を使って関数を定義(ES6 の export に対応)
module.exports = function() {
  console.log('ログ出力');
}

エントリーポイントとなるファイルで require を使ってモジュールを読み込みます。

main.js(エントリーポイント)
//require() を使用してモジュールを読み込む
const testModule = require('./node_module.js');
testModule();

ターミナルで node コマンドの引数にエントリーポイントを指定して実行します。

Node.js の環境の場合、console.log の出力先は標準出力になります。

$ node main.js  
ログ出力  //ターミナル(標準出力)での表示

Node.js を使って ES Modules の import を使う場合はモジュールのファイルの拡張子を .mjs にするか、package.json というファイルで "type": "module" を指定する必要があります。

以下は拡張子を .mjs にしたモジュールを読み込む例です。

module.mjs(拡張子が mjs)
export const test = function() {
  console.log('ログ出力');
}
main.mjs(拡張子が mjs)
import { test } from './module.mjs'
test();

ターミナルで node コマンドを実行するとモジュールの関数が実行されます。

$ node main.mjs
ログ出力  //ターミナル(標準出力)での表示

Symbol

Symbol は ES6 から追加された新たなプリミティブ型のデータ型です。

Symbol データ型の値(シンボル値)は、Symbol 関数を呼び出すことで動的に一意で不変な値として生成することができます。

以下が Symbol 関数の構文です。

Symbol( description )

引数にはオプションで description(説明のための文字列)を指定できます。この値はデバッグ目的のみに利用されます。

Symbol 値は固有の識別子(一意で不変な値)を表し、一度作成したシンボル値はそれ自身とのみ等しくなり、ユニークなIDとして機能します。

//シンボル値を生成
const mySym1 = Symbol("foo");
//上記と同じ description でシンボル値を生成
const mySym2 = Symbol("foo");

//typeof 演算子で確認
console.log(typeof mySym1);  //symbol

//自身とのみ一致
console.log(mySym1 === mySym1);  //true
console.log(mySym1.valueOf() === mySym1.valueOf());  //true

//自身以外とは一致しない
console.log(mySym1 === mySym2);  //false
console.log(Symbol("foo") === Symbol("foo"));  //false
console.log(mySym1.valueOf() === mySym2.valueOf());  //false

シンボルは文字列に「自動変換」されない

JavaScript の大部分の値は文字列への暗黙的な自動変換が行われますが、シンボルは特殊で自動変換は行われません。

const mySym1 = Symbol();
//以下の alert() はエラー
alert(mySym1);  //Cannot convert a Symbol value to a string
alert(mySym1.valueOf());   //Cannot convert a Symbol value to a string
  
//console.log() はそのままが出力される
console.log(mySym1);  //Symbol()
console.log(mySym1.valueOf());  //Symbol()

//但し、やはり暗黙的に文字列型に変換しようとするとエラー
console.log(mySym1 + '');  //Cannot convert a Symbol value to a string
console.log(mySym1.valueOf() + '');  //Cannot convert a Symbol value to a string

文字列として表示するには .toString() を使います。

const mySym1 = Symbol();
const mySym2 = Symbol("foo");

console.log(mySym1.toString()); //Symbol()
console.log(mySym2.toString()); //Symbol(foo) 

symbol.description プロパティを使用してディスクリプションを取得することができます。

const mySym1 = Symbol();
const mySym2 = Symbol("foo");

console.log(mySym1.description);  //undefined
console.log(mySym2.description);  //foo

Well-Known Symbols

ユーザーの定義する symbol に加えて、JavaScript は言語内部のふるまいを表す組み込みの symbol(Well-Known Symbols)を持っています。

例えば iterator という Well-Known Symbols は、Symbol.iterator として参照可能で、オブジェクトのための既定のイテレーターを返すメソッドです。

また、@@iterator は Symbol.iterator を表します。

オブジェクトのキーに使用

オブジェクトのプロパティ名(キー)は暗黙的に文字列に変換されてしまいます。

以下の場合、オブジェクトのキーは文字列になり、シンボルではありません。

const sym = Symbol();  //Symbol を作成
const obj = {
  sym : 'foo'   //キー sym は文字列
}
console.log(obj.sym);  //foo ( . で文字列でアクセス)
console.log(obj[sym]);  //undefined (シンボルでアクセス)

関連項目:オブジェクトのキーに変数を使う

以下のように空のオブジェクトを生成してから、プロパティに [ ] を使って指定すれば、キー(プロパティ名)にシンボルを指定することができます。

const sym = Symbol();  //Symbol を作成
const obj = {}
obj[sym] = 'foo'; //キー sym は Symbol
console.log(obj[sym]);  //foo

また、ES6(ECMAScript 2015)からは新しい記法が追加され、プロパティ名を[ ]で囲むことでプロパティ名を式にすることができるようになりました(算出プロパティ)。

const str1 = 'property', str2 = 'name';
const obj = {
  [str1 + str2] : 'bar' //[ ] の中の式が展開(計算)される
}
console.log(obj.propertyname);  //bar

この記法を使えば、以下のようにシンボルをプロパティ名に指定することができます。

const sym = Symbol();
const obj = {
  [sym] : 'foo'   //キー [sym] は Symbol
}
console.log(obj[sym]);  //foo

Map

Map は ES6(ECMAScript 2015)で導入されたキーと値の組み合わせからなるビルトインオブジェクトです。

Map と Object はどちらもキーに値を設定したり指定したキーの値を取得できるなど似ていますが、以下のような違いがあります。

Map と Object
Map Object
キーの型 あらゆる値を指定可能 (関数、オブジェクト、全てのプリミティブなど) String(文字列)または Symbol のみ
キーの順序 キーは順序が守られます。反復処理の際は挿入順でキーを返します。 キーは順不同
既定のキー 既定では何もキーを持っていません Object にはプロトタイプがあり、既定のキーを含んでいるので自分のキーと衝突する可能性があります。
大きさ Map インスタンスの size プロパティで項目数(要素数)を取得できます。 手動で取得する必要があります。
反復処理 iterable(反復可能オブジェクト)なので直接反復処理を行うことができます。for...of 文が使えます。 キーの一覧を取得して反復処理を行う必要があります。
性能 キーと値の組を頻繁に追加したり削除したりするのに適しています キーと値の組を頻繁に追加したり削除したりすることに最適化されていません。

Map インスタンスのプロパティ

プロパティ 説明
size 現在のマップに含まれる要素(キーと値の組)の数

Map インスタンスのメソッド

メソッド 説明
set(key, value) 指定した key に対応する値を持つ要素をマップに追加します。同じキーで追加を行うと、後から追加された値で上書きされます。また set() は map 自身を返すので呼び出しをチェーンすることができます。
get(key) key で指定されたキーに対応する値を返します。値や要素が存在しない場合は undefined を返します。
has(key) key で指定されたキーに対応する要素がマップ内に存在するかどうかを示す真偽値を返します。
delete(key) key で指定した要素を削除します。指定した要素が削除された場合は true を返し、要素が存在しなければ false を返します。
clear() 全てのデータ(要素)を削除します。
forEach(callback) マップの全ての要素をコールバック関数で挿入順に反復処理します。
keys() マップの全ての要素のキーを挿入順に並べた Iterator オブジェクトを返します。
values() マップの全ての要素の値を挿入順に並べた Iterator オブジェクトを返します。
entries() マップの全ての要素の配列を挿入順に並べた Iterator オブジェクトを返します。
[@@iterator]() マップの全ての要素の [key, value] の配列が挿入順で含まれる Iterator オブジェクトを返します。@@iterator は Symbol.iterator のことです。entries() と同じ。

マップの作成

new Map() で新しいマップオブジェクトを作ることができます。

以下のようにコンストラクタの引数に何も渡さない場合、中身が空(size が0)の Map オブジェクトが生成されます。

//中身が空の Map オブジェクトを作成
const myMap = new Map();

console.log(myMap);  //Map(0) 
console.log(myMap.size);  //0 (size が0)

コンストラクタの引数に初期値を渡してマップを作成することもできます。コンストラクタの引数に渡せるのはエントリー(キーと値からなる2要素の配列)の配列です。

エントリー(配列): [key, value]

new Map([[key, value] , [key, value] ...]);
//初期値に1つのエントリー(要素)を指定してマップを作成
const myMap = new Map([['key1', 'value1']]); 
 
console.log(myMap);  //Map(1) {"key1" => "value1"}
console.log(myMap.size);  //1(要素数)
console.log(myMap.get('key1')); //value1 (key1 の値)
  
//初期値に2つのエントリー(要素)を指定してマップを作成
const myMap2 = new Map([['k1', 'v1'], ['k2', 'v2']]); 
 
console.log(myMap2); //Map(2) {"k1" => "v1", "k2" => "v2"}
console.log(myMap2.size);  //2(要素数)
console.log(myMap2.get('k1')); //v1(k1 の値)
console.log(myMap2.get('k2')); //v2(k2 の値)

2次元配列をコンストラクタの引数に指定して2次元配列からマップを作成することができます。

//キー・値の2次元配列
let myArray = [["キー1", "値1"], ["キー2", "値2"]];

//コンストラクタを使ってキー・値の2次元配列をマップに変換(マップを作成)
let myMap = new Map(myArray);

console.log(myMap.size);  //2 
console.log(myMap.get('キー1')); //値1
console.log(myMap.get('キー2')); //値2

要素の追加や値の取得

map.set(key, value)  //要素の追加
map.get(key)  //値の取得

set() メソッドは特定のキーと値を持つ要素をマップに追加します。同じキーで追加を行うと、後から追加された値で上書きされます(変更になります)。

get() メソッドは指定したキーにひもづいた値を取得します。

// 新しいマップの作成
const myMap = new Map();

// 新しい要素の追加
myMap.set("key1", "value1");
console.log(myMap.size); // 1
console.log(myMap.get("key1")); //value1

// 新しい要素の追加
myMap.set("key2", "value2");
console.log(myMap.size); // 2
console.log(myMap.get("key2")); // value2

// 要素の上書き
myMap.set("key1", "new value1");
console.log(myMap.get("key1")); // new value1

オブジェクトとは異なり任意の型のキーが利用可能です(キーは文字列には変換されません)。

// 新しいマップの作成
let myMap = new Map();

//キーと成る変数を作成
let foo = { name: 'FOO' };
let bar = function(val) { console.log(val); }
let boo = 'boo';

// マップに要素を追加して値を設定
myMap.set(foo, "foo(オブジェクト)と結び付けられた値");
myMap.set(bar, "bar(関数)と結び付けられた値");
myMap.set(boo, "boo(文字列)と結び付けられた値");
  
// キーにひもづいた値を取得(変数を指定)
console.log(myMap.size);  //3
console.log(myMap.get(foo));  //foo(オブジェクト)と結び付けられた値
console.log(myMap.get(bar));  //bar(関数)と結び付けられた値
console.log(myMap.get(boo));  //boo(文字列)と結び付けられた値
 
// キーに文字列を指定(オブジェクトとは異なりキーは文字列には変換されない)
console.log(myMap.get('foo'));  //undefined (foo !== 'foo')
console.log(myMap.get('bar'));  //undefined (bar !== 'bar')
console.log(myMap.get('boo'));  //boo(文字列)と結び付けられた値 (boo === 'boo') 

set() は map 自身を返すので呼び出しをチェーンすることができます。

// 新しいマップの作成
let myMap = new Map();

// set() をチェーンして呼び出して要素を追加
myMap.set('key1', 'value1')
     .set('key2', 'value2')
     .set('key3', 'value3');

console.log(myMap.size);  //3

// 値を出力
console.log(myMap.get('key1'));  // value1
console.log(myMap.get('key2'));  // value2
console.log(myMap.get('key3'));  // value3

要素の確認

map.has(key)  //存在するかを確認

has() メソッドを使えば、指定したキーにひもづいた要素が存在するかどうかを確認することができます。

但し、要素の値については確認しないのでキーだけが設定されていて値がない場合(undefined)や null でも true が返されます。

// 新しいマップの作成
let myMap = new Map();

// set() をチェーンして要素を追加
myMap.set('key1', 'value1')
     .set('key2', null)
     .set('key3', undefined)
     .set('key4');

console.log(myMap.size);  //4

// 値を出力
console.log(myMap.get('key1'));  // value1
console.log(myMap.get('key2'));  // null
console.log(myMap.get('key3'));  // undefined 
console.log(myMap.get('key4'));  // undefined(値が存在しない)
console.log(myMap.get('key5'));  // undefined(要素が存在しない)

// 要素の存在(有無)を確認
console.log(myMap.has('key1'));  // true
console.log(myMap.has('key2'));  // true(値が null でも true)
console.log(myMap.has('key3'));  // true(値が undefined でも true)
console.log(myMap.has('key4'));  // true(要素は存在すれば値がなくても true)
console.log(myMap.has('key5'));  // false(要素が存在しない)

要素の削除

map.delete(key) //指定した要素を削除
map.clear() //全ての要素を削除

delete() メソッドは key で指定された要素をマップから削除します。

clear() メソッドはマップが持つ全ての要素を削除します。

// 3つの要素を持つ新しいマップを作成
let myMap = new Map([['key1','value1'],['key2','value2'],['key3','value3']]);
 
console.log(myMap.size);  //3

//キーが key2 の要素を削除
myMap.delete('key2');
console.log(myMap.has('key2'));  //false
console.log(myMap.size);  //2

////全ての要素を削除
myMap.clear();  
console.log(myMap.size);  //0
console.log(myMap);  //Map(0) {}(空のマップ)

マップの反復処理

マップの反復(ループ・繰り返し)処理を行うメソッドには以下の4つがあります。

map.forEach(callback) //全ての要素をコールバック関数で挿入順に反復処理
map.keys() //全ての要素のキーを挿入順に並べた Iterator オブジェクトを返す
map.values() //全ての要素の値を挿入順に並べた Iterator オブジェクトを返す
map.entries() //全ての要素を挿入順に並べた Iterator オブジェクトを返す

forEach()

マップの全ての要素を指定したコールバック関数で挿入順に反復処理します。

map.forEach(callback[, thisArg])
引数 説明
callback それぞれの要素に対して実行するコールバック関数。以下の引数が渡されます。
  • 要素の値
  • 要素のキー
  • 実行される Map オブジェクト
thisArg (オプション)コールバック関数(callback)を実行するときに、thisとして使う値

戻り値:値を返しません(undefined)

以下は forEach() を使って全ての要素のキーと値を出力する例です。

// 3つの要素を持つ新しいマップを作成
const myMap = new Map([['key1', 'value1'],['key2', 'value2'], ['key3', 'value3']]);

// マップの要素を挿入順に処理
myMap.forEach(function(value, key) {
  // キーと値をテンプレートリテラルで出力
  console.log(`key: ${key} / value: ${value}`);
})

/*出力結果
key: key1 / value: value1
key: key2 / value: value2
key: key3 / value: value3
*/

以下は forEach() を使って全ての要素の値を大文字に変換する例です。コールバック関数では3つ目の引数にマップ自身(以下の場合は myMap)を受け取れます。

// 3つの要素を持つ新しいマップを作成
const myMap = new Map([
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
]);

// コールバック関数の3つ目の引数(map)を使って自身の要素の値を変更
myMap.forEach(function(value, key, map) {
  // 値を大文字に変換
  map.set(key, value.toUpperCase());
})
 
myMap.forEach(function(value, key, map) {
  console.log(`key: ${key} / value: ${value}`);
})

/*出力結果(値が大文字に変換されている)
key: key1 / value: VALUE1
key: key2 / value: VALUE2
key: key3 / value: VALUE3
*/

keys()、values()、entries()

keys()、values()、entries() の3つのメソッドの戻り値は Iterator オブジェクトで、next() メソッドを持ち、next() メソッドの戻り値は value と done という2つのプロパティを持っています。

また、keys()、values()、entries() の戻り値は for...of 文を使って反復処理が可能です。Array.from() メソッドやスプレッド構文を使って配列に変換することもできます。

keys() メソッドはマップの全ての要素のキーを挿入順に並べた Iterator オブジェクトを返します。

const myMap = new Map([
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
]);

//全ての要素のキーを挿入順に並べた Iterator オブジェクト
const myMapKeys = myMap.keys();
        
//イテレータの next() メソッドの戻り値の value プロパティ(キー)を出力
console.log(myMapKeys.next().value); //key1
console.log(myMapKeys.next().value); //key2
console.log(myMapKeys.next().value); //key3
console.log(myMapKeys.next().value); //undefined
        
// for...of を使って全ての要素のキーを挿入順に出力
for(const key of myMap.keys()) {
  console.log(key);
}

/*出力結果
key1
key2
key3
*/

以下は配列に変換する例です。

const myMap = new Map([
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
]);

//Array.from() に渡して配列に変換       
const keys_array = Array.from(myMap.keys());  

console.log(keys_array);  //["key1", "key2", "key3"]

//キーの Iterator オブジェクト
const keys = myMap.keys();   
//スプレッド構文を使って配列に変換
const keys_array2 =  [...keys];

console.log(keys_array2);  //["key1", "key2", "key3"]      

values() メソッドはマップの全ての要素の値を挿入順に並べた Iterator オブジェクトを返します。

const myMap = new Map([
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
]);
        
//全ての要素の値を挿入順に並べた Iterator オブジェクト
const myMapValues = myMap.values();
        
//イテレータの next() メソッドの戻り値の value プロパティ(値)を出力
console.log(myMapValues.next().value); //value1
console.log(myMapValues.next().value); //value2
console.log(myMapValues.next().value); //value3
console.log(myMapValues.next().value); //undefined
        
// for...of を使って全ての要素の値を挿入順に出力
for(const value of myMap.values()) {
  console.log(value);
}
 
/*出力結果
value1
value2
value3
*/
        
// Array.from() に渡して配列に変換       
const values_array = Array.from(myMap.values());       
console.log(values_array);  //["value1", "value2", "value3"]
        
        
//値の Iterator オブジェクト       
const values = myMap.values();       
//スプレッド構文を使って配列に変換
const values_array2 =  [...values];

console.log(values_array2);  //["value1", "value2", "value3"]  

entries() メソッドはマップの全ての要素の[キー、値]の配列(エントリー)を挿入順に並べた Iterator オブジェクトをそれぞれ返します。

const myMap = new Map([
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
]);
        
//全ての要素のエントリーを挿入順に並べた Iterator オブジェクト
const myMapEntries = myMap.entries();
        
//イテレータの next() メソッドの戻り値の value プロパティ(エントリー)を出力
console.log(myMapEntries.next().value); // ["key1", "value1"]
console.log(myMapEntries.next().value); // ["key2", "value2"]
console.log(myMapEntries.next().value); // ["key3", "value3"]
console.log(myMapEntries.next().value); //  undefined
        
// for...of を使って全ての要素のエントリーを挿入順に出力
for(const entry of myMap.entries()) {
  console.log(entry);
}

/*出力結果
["key1", "value1"]
["key2", "value2"]
["key3", "value3"]
*/

エントリーは [key, value] の形式の配列なので、for...of 文で配列の分割代入を使うとエントリーからキーと値を簡単に取り出すことができます。

また、マップ自身も反復可能オブジェクトなので、for...of 文を使って反復処理できます。マップを for...of 文で反復処理するのと entries() メソッドの戻り値を反復処理するのは同じ結果になります。

const myMap = new Map([
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
]);
        
// for...of と分割代入を使って全ての要素のキーと値を挿入順に出力
for(const [key, value]  of myMap.entries()) {
  console.log(`${key} : ${value}`);
}

/*出力結果
key1 : value1
key2 : value2
key3 : value3
*/

//マップを for...of で反復処理(上記と同じこと)
for(const [key, value]  of myMap) {
  console.log(`${key} : ${value}`);
}
 /*出力結果(前の結果と同じ)
key1 : value1
key2 : value2
key3 : value3
*/ 

// 以下も同じこと   
for(const [key, value]  of myMap[Symbol.iterator]()) {
  console.log(`${key} : ${value}`);
}  
 /*出力結果(前の結果と同じ)
key1 : value1
key2 : value2
key3 : value3
*/ 

マップと配列

マップは反復可能オブジェクトなので、Array.from() メソッドやスプレッド構文を使ってマップから新たに配列を生成することができます。

//3つの [key, value] から成る2次元配列
const array = [
  ['key1','value1'],
  ['key2','value2'],
  ['key3','value3']
];
// 上記配列を引数に指定して3つの要素を持つマップを作成
const map = new Map(array);
        
console.log(map.size); //3
console.log(map.get('key2'));   //value2
 
//Array.from を使って array と同じ配列を生成
const array2 = Array.from(map);
console.log(array2);  
/*(3) [Array(2), Array(2), Array(2)]
0: (2) ["key1", "value1"]
1: (2) ["key2", "value2"]
2: (2) ["key3", "value3"]
length: 3 */  

//スプレッド構文を使って array と同じ配列を生成
const array3 = [...map];       
console.log(array3); 
/*(3) [Array(2), Array(2), Array(2)]
0: (2) ["key1", "value1"]
1: (2) ["key2", "value2"]
2: (2) ["key3", "value3"]
length: 3 */ 

MDN Map

Set

Set は ES6(ECMAScript 2015)で導入された重複した値がないコレクションを扱うことができるビルトインオブジェクトです。

配列に似ていますが、配列と異なりインデックスによるアクセスはできません。また、配列では重複した値を複数格納できますが Set では重複したデータを持てません。

Set はプリミティブ値・オブジェクト参照を問わず、あらゆる型で一意の値を格納できます。

Set インスタンスのプロパティ

プロパティ 説明
size 現在のセットに含まれる値の数

Set インスタンスのメソッド

メソッド 説明
add(value) 引数で与えられた値を持つ新しい要素をセットに追加します。すでに同じ値がセット内にある場合は無視されます。(エラーにはなりません。)。また add() はセット自身を返すので呼び出しをチェーンすることができます。
has(value) 引数で与えられた値をもつ要素が存在するかどうかを示す真偽値を返します。
delete(value) 引数で指定した値を削除します。value が呼び出し時に存在すれば true、そうでなければ false を返します。
clear() 全ての値を削除します。
forEach(callback) セットの全ての値をコールバック関数で挿入順に反復処理します。
values() セットの全ての要素の値を挿入順に並べた Iterator オブジェクトを返します。
entries() セットの全ての要素の [value, value] の配列を挿入順に並べた Iterator オブジェクトを返します。これは Map オブジェクトに似させているためで、各エントリーは key と value が同じ値になります。
[@@iterator]() セットの全ての要素の [value, value] の配列が挿入順で含まれる Iterator オブジェクトを返します。@@iterator は Symbol.iterator のことです。entries() と同じ。

セットの作成

new Set() で新しいセットオブジェクトを作ることができます。

以下のようにコンストラクタの引数に何も渡さない場合、中身が空(size が0)のセットオブジェクトが生成されます。

//中身が空の Set オブジェクトを作成
const mySet = new Set();
 
console.log(mySet);  //Set(0) {} 
console.log(mySet.size);  //0 (size が0)

コンストラクタの引数に初期値(反復可能オブジェクト)を渡してセットを作成することもできます。反復可能オブジェクトが渡されたら、その全ての要素が新しい Set オブジェクトに追加されます。

//配列(反復可能オブジェクト)を渡して Set オブジェクトを作成
const mySet = new Set(['foo', 'bar', 'boo']);
 
console.log(mySet);  //Set(3) {"foo", "bar", "boo"}
console.log(mySet.size);  //3
 
//重複した値を持つ配列を渡して Set オブジェクトを作成
const mySet2 = new Set([1, 2, 3, 1]);

//1 は重複するため片方は無視される
console.log(mySet2);  //Set(3) {1, 2, 3}
console.log(mySet2.size);  //3 (サイズは3)

配列から重複した要素を取り除く

セットを使うと簡単に配列から重複した要素を取り除くことができます。

重複した要素を取り除きたい配列をコンストラクタの引数に指定してセットを生成し、Array.from() やスプレッド構文で配列に変換します(順番は保持されます)。

const array = [1, 2, 4, 2, 3 , 3, 2, 4, 5, 7, 5, 6, 1, 2];
        
//配列をコンストラクタの引数に指定して重複のないセットを生成し、Array.from()で変換
const unique_array = Array.from(new Set(array));
console.log(unique_array);  //[1, 2, 4, 3, 5, 7, 6]

//プレッド構文で配列に変換
const unique_array2 = [...new Set(array)];
console.log(unique_array2);  //[1, 2, 4, 3, 5, 7, 6]

要素の追加と削除

add() メソッドは引数で与えられた値を持つ新しい要素をセットに追加し、has() メソッドは引数で指定した値(の要素)を削除します。clear() メソッドを使えば、全ての値を削除することができます。

has() メソッドは引数で与えられた値をもつ要素が存在するかどうかを示す真偽値を返します。

const mySet = new Set();
        
mySet.add(1);  //数値を追加
mySet.add('a');  //文字列を追加
let foo = {name: 'foo'};
mySet.add(foo);  //オブジェクトの参照を追加
console.log(mySet);  //Set(3) {1, "a", {…}}
console.log(mySet.size);  //3

mySet.add(2).add(3).add(4);  //チェーン呼び出しで要素を3つ追加
console.log(mySet.size);  //6

//特定の値を持っているかどうかを確認
console.log(mySet.has(1));  //true
console.log(mySet.has('a'));  //true
console.log(mySet.has(foo));  //true
console.log(mySet.has({name: 'foo'})); //false
        
mySet.delete(foo);  //値(要素)を削除
mySet.delete(1);  //値(要素)を削除
               
console.log(mySet.size); //4

mySet.clear();  //全ての値を削除
console.log(mySet.size); //0

例えば、ユーザのアクセスを重複なしに調べたい場合などにセットは便利です。

const mySet = new Set();  //空のセットを作成
        
let foo = {name: 'foo'};
let bar = {name: 'bar'};
let boo = {name: 'boo'};

//アクセスがある度にセットにユーザーを追加
mySet.add(foo);
mySet.add(bar);
mySet.add(foo);
mySet.add(bar);
mySet.add(foo);
mySet.add(bar);
        
//セットはユニークな値のみを保持
console.log(mySet.size);  //2

//セットは for...of 文が使えます
for(const user of mySet) {
  console.log(user.name);
}
/*
foo
bar
*/

mySet.clear();  //全てのデータを削除
console.log(mySet.size); //0 

セットの反復処理

forEach() メソッドを使うとセットが持つ全ての要素をセットへの挿入順に反復処理することができます。

set.forEach(callback[, thisArg])
引数 説明
callback それぞれの要素に対して実行するコールバック関数。以下の引数が渡されます。※2つ目の引数(要素のキー)は Map との互換性のために作られていて、セットにはキーは存在しないので要素の値が入っています。
  • 要素の値
  • 要素のキー(※実際には一番目の引数と同じ要素の値が渡されます)
  • 実行される Set オブジェクト
thisArg (オプション)コールバック関数(callback)を実行するときに、thisとして使う値

戻り値:値を返しません(undefined)

以下は forEach() を使って全ての要素(値)を出力する例です。

const mySet = new Set(['foo', 'bar', 'boo']);
        
mySet.forEach(function(val) {
  console.log('value: ' + val);
})
/*        
value: foo
value: bar
value: boo
*/  

for...of 文

セットは反復可能オブジェクトなので for...of 文を使って反復処理が可能です。

const mySet = new Set(['foo', 'bar', 'boo']);
        
// for...of 文を使って反復処理       
for(const val of mySet) {
  console.log('value: ' + val);
}
/*        
value: foo
value: bar
value: boo
*/  
        
// 以下でも同じ結果になります。      
for(const val of mySet.values()) {
  console.log('value: ' + val);
}

/*        
value: foo
value: bar
value: boo
*/ 

//.entries() は [value, value] になるので、以下の場合は値が重複して出力されます   
for(const val of mySet.entries()) {
  console.log('value: ' + val);
}
        
/*       
value: foo,foo
value: bar,bar
value: boo,boo
*/      
 

MDN Set

クラス構文

ES6(ECMAScript 2015)からクラス構文が追加され、他のプログラミング言語のクラスに似たような記述ができるようになっています。

但し、Javascript にはクラスという仕組みはなく、今までのプロトタイプベースの仕組み(関数でクラスを表現)を class キーワードを使ったクラス構文で定義できるようになっただけで、新しいオブジェクト指向継承モデルが導入されたわけではありません。

クラスの定義

クラスを定義するには class 構文を使います。クラス構文にはクラス宣言とクラス式という2つの定義方法があります。

クラスは class キーワードを使って宣言し、クラス名(以下の例では MyClass)は頭文字を大文字にします。また、クラス本体は Strict モードで実行されます。

クラス宣言
class MyClass {
  constructor() {  // コンストラクタ
    //処理の記述
  }
  ・・・その他のメソッド・・・
}

クラス式は、名前付きでも名前なしでも定義できます。名前付きクラスの名前は、クラス内のローカルとして扱われ、クラスの name プロパティによって取得できます。

クラス式
// 名前なし
let MyClass = class {
  constructor() {  // コンストラクタ
    //処理の記述
  }
  ・・・その他のメソッド・・・
};

// 名前付き(MyClass2 は class の内側でだけ見えます)
let MyClass = class MyClass2 {
  constructor() {  // コンストラクタ
    //処理の記述
  }
  ・・・その他のメソッド・・・
};

コンストラクタ

コンストラクタはクラスで定義したオブジェクトが生成される際に実行されるメソッドで、constructor という名前の関数として定義します。

constructor というメソッドは1つのクラスに1つしか定義できません。コンストラクタは主にクラス内で共通して使われるプロパティの初期値などを定義します。

また、クラスはデフォルトのコンストラクタを持って、class 構造の中にコンストラクタがない場合、空の関数が生成され constructor() {} と書いたのと同じように動作します。

定義できるのはメソッドのみ

クラス直下の本体部分で定義できるのはメソッドのみです。インスタンスのプロパティはクラスのコンストラクタやメソッドの中で定義しなければなりません。

プロトタイプメソッド

class ブロック内でコンストラクタとは別に関数を定義するとプロトタイプメソッドを宣言したことになります。プロトタイプメソッドとは、クラスのインスタンスから呼び出せる(プロトタイプに設定された)メソッドです。

インスタンスメソッド

コンストラクタの中で定義した関数をインスタンスメソッドと呼びます。インスタンスメソッドもクラスのインスタンスから呼び出せるメソッドです(関連:インスタンスメソッド)。

プロパティ

オブジェクトリテラルとは異なり、class ブロック内で property : value の定義はできません。クラスにプロパティ(インスタンス変数)を持たせる場合は、コンストラクタ内で記述します。または、getter や setter メソッド(アクセッサプロパティ)を使うことで対応することができます。

インスタンスのプロパティ(インスタンス変数)を定義したり参照するには、this.変数(プロパティ名) とします。this.変数 で定義される変数はプロパティ名としてアクセスすることができます。

prototype にプロパティを設定することも可能ですが、その場合、全てのオブジェクト(インスタンス)に対してプロパティを追加することになり非効率になります。

以下は class 構文を使ったシンプルなクラス Person の例です。

class 構文
class Person {
  constructor(name) {  //コンストラクタ
    this.name = name;  //インスタンス変数
  }

  hello() {  //メソッド(プロトタイプメソッド)
    alert('My name is ' + this.name);
  }
}

let foo = new Person("Foo"); //オブジェクトの生成(インスタンス化)は new を使います
console.log(foo.name);   //Foo
foo.hello();  //My name is Foo

上記の class 構文の中では以下のことが行われています。

  • constructor という名前の関数を参照する変数 Person を宣言
  • ブロックの中で定義されているメソッド(constructor と hello)を Person.prototype に設定

class 構文はコンストラクタとプロトタイプメソッドを一緒に定義する特別な構文と言えます。

以下は、上記の class 構文と同じことをプロトタイプベースのクラスで書き換えたものです。

プロトタイプベースのクラス
function Person(name) {  //コンストラクタ関数
  this.name = name;
}

Person.prototype.hello = function() {  //プロトタイプメソッド
  alert('My name is ' + this.name);
}

let foo = new Person("Foo");
console.log(foo.name);   //Foo
foo.hello();  //My name is Foo

メソッドの定義

class ブロック内で constructor 以外の名前で定義した関数はプロトタイプメソッドになります。

プロトタイプメソッドはプロトタイプオブジェクト(オブジェクトの prototype プロパティに自動的に作成されるオブジェクト)へ定義されたメソッドで、クラスのインスタンスから呼び出すことができます。

また、コンストラクタの中で定義するインスタンスオブジェクトに対するメソッドをインスタンスメソッドと呼びます。

プロトタイプメソッドとインスタンスメソッドのどちらもインスタンスから呼び出すことができます。

プロトタイプメソッドとインスタンスメソッドに同じ名前のメソッドを定義した場合、その名前のメソッドを呼び出すとプロトタイプチェーンと呼ばれる仕組みによりインスタンスメソッドが呼び出されます。

class Person {
  constructor(name) {
    this.name = name;
    //インスタンスメソッド myMethod
    this.myMethod = () => {
      alert('Instance Method');
    }
  }
  
  //プロトタイプメソッド myMethod
  myMethod() {
    alert('Prototype Method');
  }
}

let foo = new Person("Foo");
foo.myMethod(); //Instance Method(インスタンスメソッドが呼び出されます)

上記の場合、インスタンスメソッドを削除するか別名にするとプロトタイプメソッドが呼び出されます。

プロトタイプメソッドはプロトタイプオブジェクトへ定義され、インスタンスメソッドはインスタンスオブジェクトへ定義されます。

また、JavaScript にはプロトタイプチェーンと呼ばれる仕組みがあり、オブジェクトのプロパティを読み出す際は最初にオブジェクトにプロパティがあるかをチェックしあればそれを読み出し、ない場合はオブジェクトのプロトタイプオブジェクトにプロパティがあるかをチェックするようになっています。

静的(static)メソッド

プロトタイプメソッドやインスタンスメソッドはクラスから生成したインスタンスから呼び出せるメソッドですが、クラスをインスタンス化せずに利用できるメソッドもあり、静的メソッド(クラスメソッド)と呼ばれます。

静的(static)メソッドは、クラスには属するけれど、特定のオブジェクト(インスタンス)には属さない関数を実装するのに使用されます。

クラスに静的(static)メソッドを定義するには、static キーワードを使用します。

静的メソッドはクラスのインスタンスを生成することなく「クラス名.メソッド名」で呼び出すことができます。インスタンスから呼び出すことはできません。

以下は引数に可変長の Person クラスのインスタンスを受け取り、コンソールにメッセージを出力する Person.makeFriends という静的メソッドを定義する例です。

クラスの静的メソッドにおける this はそのクラス自身を参照します。そのため、以下の10行目の this.name はクラスの name プロパティ(Person)になり、インスタンスの name プロパティとは異なります。

class Person {
  // コンストラクタ
  constructor(name) {
    this.name = name;
  }
  
  //静的メソッド(引数に可変長のインスタンスを受け取る)
  static makeFriends(...persons) {
    // 以下の this はクラス自身(静的メソッドにおける this はそのクラス自身)
    console.log('Message from ' + this.name + ' クラス'); 
    for(let person of persons) {
      //渡されたインスタンスの name プロパティを使って出力
      console.log(person.name + ' is my friend.');
    }
  }
}

// インスタンスを生成
let foo = new Person("Foo");
let bar = new Person("Bar");
let boo = new Person("Boo");

// クラス名.メソッド名 で呼び出し
Person.makeFriends(foo, bar, boo);

/* 出力
Message from Person クラス
Foo is my friend.
Bar is my friend.
Boo is my friend.
*/

getter / setter

クラスでは ES5 で導入された getter や setter のアクセッサプロパティを定義できます。

通常のメソッドは メソッド名( ) のように呼び出しますが、getter や setter はプロパティの参照や代入の際に .メソッド名 で呼び出す「見た目は変数だけれど実際は関数」と言う特殊なメソッドです。

プロパティのように振る舞うのでアクセッサプロパティとも呼ばれます。

this.プロパティ名 は単なる変数ですが、getter や setter はメソッドなので値を取得する際に加工したり、設定時に値を検証するなどの処理を行うことができます。

以下が構文です。

get プロパティ名() { 値を取得する(返す)コード }
set プロパティ名(value) { 値を設定するコード }
アクセッサプロパティ 説明
getter(ゲッター) プロパティの参照時に呼び出され値を取得する関数
setter(セッター) プロパティへの代入時に呼び出され値を設定する関数。設定に使用する値を引数に受け取ります。

以下は MyGet クラスの name プロパティを定義する getter(ゲッター)の例です。

name プロパティは getter により生成される擬似的なプロパティで、実際に値を保持するのは this._name( _name プロパティ)です。get name() でも取得して返しているのは this._name の値です。

class MyGet {
  constructor(name) {
    this._name = name;  // _name プロパティ 
  }
  get name() {  // getter(name プロパティを定義)
    return this._name;  //実際には _name を返す
  }
}  
  
let foo = new MyGet('Foo');
// foo の name プロパティを取得(getter が呼び出される)
console.log(foo.name); //Foo
// 以下の代入はできない(setter を定義すれば代入可能)
foo.name = 'bar';  
console.log(foo.name);  //Foo

console.log(foo); 
/* 以下は上記の出力をブラウザのインスペクタで確認した結果
MyGet {_name: "Foo"}
  _name: "Foo"  // _name プロパティ 
  name: "Foo"  //getter により生成た name プロパティ
  __proto__:
    constructor: class MyGet
    name: "Foo"
    get name: ƒ name()*/

プロパティの値を保持する変数の名前(上記の場合は _name)は getter のプロパティ名(上記の場合は name)とは異なる名前にする必要があります。

this._name がアンダスコアで始まるのはクラスの外から利用してほしくないことを表す慣習的な書き方で、構文としての意味はありません(プライベートになるわけではありません)が運用上のルールとしてはわかりやすいものです。

例えば以下のように別の名前(myName)にすることもできます。

class MyGet {
  constructor(name) {
    this.myName = name;  // myName プロパティ
  }
  get name() {  // getter (name プロパティを定義)
    return this.myName;  //実際には myName を返す
  }
}  
  
let foo = new MyGet('Foo');
// foo の name プロパティを取得
console.log(foo.name); //Foo

console.log(foo);
/* 以下は上記の出力をブラウザのインスペクタで確認した結果
MyGet {myName: "Foo"}
  myName: "Foo"
  name: "Foo"  //getter により生成たプロパティ
  __proto__:
    constructor: class MyGet
    name: "Foo"
    get name: ƒ name()*/

実際の値を持つプロパティが、同時に getter や setter を持つことはできないので以下の場合、エラーになります。

//実際の値を持つプロパティと getter のプロパティ名が同じだとエラー
class MyGet {
  constructor(name) {
   //Cannot set property name of #<MyGet> which has only a getter
    this.name = name;  // 実際の値を持つプロパティ(this.name)
  }
  get name() {  // getter の プロパティ名 name
    return this.name;
  }
} 

また、以下のように getter のプロパティ名 name() と取得するプロパティの名前 this.name が同じ場合、this.name は get name() を呼び出すので無限ループが発生してしまいます。

//無限ループが発生する例(6行目と8行目が同じ名前 name )
class MyGet {
  constructor(name) {
    this._name = name;  // _name プロパティ
  }
  get name() {  // getter(プロパティ名 name)
    // エラーUncaught RangeError: Maximum call stack size exceeded
    return this.name;  //取得するプロパティの名前 name
  }
} 

以下は MySet クラスの name プロパティを定義する setter(セッター)の例です。

getter 同様、name プロパティは擬似的なプロパティで、実際に値を保持するのは this._name です。set name() でも値を設定しているのは this._name です。

class MySet {
  constructor(name) {
    this._name = name;  //  name プロパティ
  }
  set name(value) {  //  setter
    if (value.length < 3) {
      alert("Too Short!");
      return;
    }
    this._name = value;
  }
}  
  
let foo = new MySet('Foo');
// name プロパティを取得 → .name ではアクセスできない(getter を定義すれば取得可能)
console.log(foo.name); //undefined 
// _name でアクセスできる
console.log(foo._name);  //Foo 
//name プロパティに Bar を設定(setter が呼び出される)
foo.name = 'Bar';  
console.log(foo._name);  //Bar
foo.name = 'X';  //alert "Too Short!" が表示される

また、getter 同様、setter のプロパティ名 name() と設定するプロパティ名(10行目)を同じ値(this.name)にしてしまうと、setter が呼び出されると無限ループが発生します。

以下は getter と setter の実装例で、Person クラスの name プロパティ(擬似的なプロパティ)をアクセッサプロパティとして定義しています。

アクセッサプロパティで実際に読み書きしているのはアンダースコアが付いた this._name です。

class Person {
  constructor(name) {
    this._name = name;  //  name プロパティ
  }
  get name() {  // getter 
    return this._name;
  }
  set name(value) {  //  setter
    if (value.length < 1) {
      alert("無効です");
      return;
    }
    this._name = value;
  }
}

let foo = new Person("Foo");
        
alert(foo.name); // Foo (getter が呼び出される)
foo.name = '';  //無効です(setter が呼び出される)
alert(foo.name);  //Foo(getter が呼び出される)
foo.name = 'Bar'; //(setter が呼び出される)
alert(foo.name);  //Bar(getter が呼び出される)

以下はアクセッサプロパティを使ってプロパティ width と height を定義する例です。

以下の場合も width や height プロパティは擬似的なプロパティで、実際に値を保持するのは this._width と this._height です。

getter では _width や _height プロパティが定義されていない場合は1を返し、setter では受け取った値が数値ではない場合は1を設定しています。

class Rectangle {
  get width() {
    return this._width || 1;
  }
  get height() {
    return this._height || 1;
  }
  set width(w) {
    if(isNaN(w)) {
      this._width = 1;
    }else{
      this._width = w;
    }
  }
  set height(h) {
    if(isNaN(h)) {
      this._height = 1;
    }else{
      this._height = h;
    }
  }
  area() {
    return this.width * this.height;
  }
}
        
const foo = new Rectangle();
console.log(foo.area());  // 1
foo.width = 5;
console.log(foo.area());  // 5
foo.height = 3;
console.log(foo);
/* 以下出力の展開結果
Rectangle {_width: 5, _height: 3}
  _height: 1
  _width: 5
  height: 1
  width: 5
*/
console.log(foo.area());  // 15
foo.height = 'a';  //数値ではないので1が設定される
foo.width = 'b';  //数値ではないので1が設定される
console.log(foo.area()); // 1 
console.log(foo);
/* 以下出力の展開結果
Rectangle {_width: 1, _height: 1}
  _height: 1
  _width: 1
  height: 1
  width: 1
*/

クラスの継承

既存のクラスを継承する(クラスの構造や機能を引き継いで新しいクラスを定義する)には extends キーワードを使います。

継承元となるクラスを親クラスや基底クラス、スーパークラス、継承したクラスを子クラスや派生クラス、サブクラスなどと呼びます。

以下は Animal クラスを継承して Cat というクラスを定義する例です。

Cat クラスは Animal クラスの構造や機能を引き継ぐので、Animal クラスの speak メソッドを使用できます。また、新規に meow というメソッドを追加しています。

以下では コンストラクタを省略しているので親クラスのコンストラクタが適用されます。

class Animal { 
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} speaks.`);
  }
}

//Animal クラスを継承する Cat クラス
class Cat extends Animal {
  //メソッドの追加
  meow() {
    console.log('meow meow meow');
  }
}

let cat = new Cat('Mike');
cat.speak(); // Mike speaks. 親のメソッドを使用可能      
cat.meow();  // meow meow meow 追加したメソッド    

メソッドのオーバーライド

子クラスで親クラスと同名のメソッドを定義するとメソッドがオーバーライドされます。

以下は親クラス Animal のメソッドを継承する際にオーバーライドする例です。

class Animal { 
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} speaks.`);
  }
}

class Cat extends Animal {
  //メソッドのオーバーライド
  speak() {
    console.log('meow meow meow');
  }
}

let cat = new Cat('Mike');
cat.speak(); //  オーバーライドしたメソッドを実行
//meow meow meow

親クラスのメソッドを完全に置き換えるのではなく機能を追加する場合などには、親のメソッドに super. を付けて親クラスのプロトタイプメソッドを呼び出すことができます 。

class Animal { 
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} speaks.`);
  }
}

class Cat extends Animal {
  //メソッドのオーバーライド
  speak() {
    //親メソッドの呼び出し
    super.speak();
    console.log('meow meow meow');
  }
}

let cat = new Cat('Mike');
cat.speak();  // オーバーライドしたメソッドを実行
/*        
Mike speaks.
meow meow meow   
*/      

コンストラクタのオーバライド

コンストラクタをオーバライドする場合は、コンストラクタで this を使う前に super() を呼び出す必要があります。

子クラスで this を参照するには、先に super() を呼び出して親クラスのコンストラクタ処理を実行してからでないと this を参照することができないためです。

以下は Shape クラスを継承して Rectangle クラスを定義する例です。新しいプロパティが必要なのでコンストラクタをオーバーライドしています。

class Shape {
  constructor(name) {
    this.name = name;
  }
  print() {
    console.log('This is ' + this.name);
  }
}
        
let foo = new Shape('circle');
foo.print();  //This is circle

//Shape クラスを継承して Rectangle クラスを定義
class Rectangle extends Shape {
  //コンストラクタをオーバライド
  constructor(height, width) {
    //親クラスのコンストラクタ処理を実行
    super(name);
    // this にアクセス
    this.name = 'Rectangle';
    this.height = height;
    this.width = width;
  }
  //親のメソッドをオーバーライド
  print() {
    //親メソッドの呼び出し
    super.print();
    //追加の処理
    console.log(`width: ${this.width} height: ${this.height}`);
  }
  //メソッドを追加
  area() {
    return this.width * this.height;
  }
}
        
let bar = new Rectangle(5,4);
bar.print();  //This is Rectangle. width: 4 height: 5
console.log(bar.area());  //20

ビルトインオブジェクトの継承

ビルトインオブジェクト(組み込みオブジェクト)も継承することができます。

以下はビルトインオブジェクトの Date を継承して独自のメソッド myWeek を追加した MyDate クラスを定義する例です。

myWeek() は Date のメソッド getDay() を使って取得した値から日本語の曜日を返すメソッドです。

class MyDate extends Date {
  //独自のメソッド myWeek を追加(その他は Date のデフォルト)
  myWeek() {
    let week = super.getDay();
    switch(week) {
      case 0:
        return '日曜日';
        break;
      case 1:
        return '月曜日';
        break;
      case 2:
        return '火曜日';
        break;
      case 3:
        return '水曜日';
        break;
      case 4:
        return '木曜日';
        break;
      case 5:
        return '金曜日';
        break;
      case 6:
        return '土曜日';
        break;
      default :
        return week;
    }
  }
}
        
const today = new MyDate();  //インスタンスを生成
console.log(today.myWeek()); //追加したメソッドを実行
//水曜日
console.log(today.toLocaleString());  // Date オブジェクトのメソッドを実行      
//2020/5/13 11:16:00        
const date2222 = new MyDate('2/22/2222');  
console.log(date2222.myWeek());  
//金曜日

Date を継承した MyADate は、Date が元から持つメソッドなどを継承しているのでそれらを利用することができます。

オブジェクト

ES6(ECMAScript 2015)ではオブジェクトリテラルに以下のような構文が追加されました。

プロパティの短縮構文

オブジェクトのキー名と値の変数名が同じ場合、省略して記述することができます。

// ES5 
var a = 'foo', b = 'bar', c = 'boo';
obj = {
  a: a,  //キー名 : 変数名(値)
  b: b,
  c: c
};

console.log(obj.a);  //foo
console.log(obj.b);  //bar
console.log(obj.c);  //boo

// ES6 短縮構文
var a = 'foo', b = 'bar', c = 'boo';
obj = { //キー名と値の変数名が同じ場合は以下のように省略可能
  a,
  b,
  c
};
console.log(obj.a);  //foo
console.log(obj.b);  //bar
console.log(obj.c);  //boo

例えば、以下のようなオブジェクトのキーと値が同じ文字であるパターンでは、繰り返し同じ文字を記述しないで済むので便利です。

function getUser(name, age, gender) {
  return {
    name: name,
    age: age,
    gender: gender
  };
}

let user = getUser('Mike', 28, 'male');
console.log(user.name); // Mike
console.log(user.age);  //28
console.log(user.gender);  //male

上記は以下のように記述することができます。

function getUser(name, age, gender) {
  return {  // ES6 短縮構文
    name,  // name: name と同じ
    age,   // age: age と同じ
    gender // gender: gender と同じ
  };
}

let user = getUser('Mike', 28, 'male');
console.log(user.name); // Mike
console.log(user.age);  //28
console.log(user.gender);  //male

算出プロパティ(算出されたキー)

ES6 からはオブジェクトのプロパティ名(キー)を [ ] で囲むことでその中に記述した式を評価した値をキーとすることができるようになっています(Computed property)。

[ ] の中では変数やシンボルなどを使うことができます。

const val1 = 'foo', val2 = 'bar';
const obj = {
  [val1 + val2] : 'BOO' //[ ] の中の式が展開(算出)される
}
console.log(obj.foobar);  //BOO

ES5 まではオブジェクトのプロパティ名(キー)に変数を使用するには、オブジェクトを生成してから使用する必要がありました。

//ES5 まで
var myKey = 'foo';  //変数
var obj = {};  //オブジェクトを生成
//オブジェクトを生成してからキーに変数を使用
obj[myKey] = 'FOO'; 
  
console.log(obj[myKey]);  //FOO
console.log(obj['foo']);  //FOO
console.log(obj.foo);  //FOO 

ES6 からはプロパティ名を[ ]で囲むことでプロパティ名に変数を使うことができます。(Dynamic Property Keys)

//ES6
let myKey = 'foo';
const obj = {
  //プロパティ名を[ ]で囲む
  [myKey]: 'FOO'
};

console.log(myKey);  // foo (変数 myKey は文字列 foo)
console.log(obj[myKey]);  //FOO (obj のプロパティ myKey )
console.log(obj['foo']);  //FOO (obj のプロパティ foo )
console.log(obj.foo);  //FOO (obj のプロパティ . アクセス )
console.log(obj); //{foo: "FOO"}

以下は配列の map() メソッドで、各要素のオブジェクトのキーにインデックス番号を追加する例です。

let contact = [
  {
    name: "Foo",
    email: "foo@example.com"
  },
  {
    name: "Bar",
    email: "bar@example.com"
  },
  {
    name: "Boo",
    email: "boo@example.com"
  },
]
 
//配列の各要素のオブジェクトのプロパティ名にインデックス番号を追加した配列を生成
let indexContact = contact.map((item, index) => {
  return {
    ["name_" + index]: item.name,
    ["email_" + index]: item.email
  }
})

console.log(indexContact[1].email_1);  //bar@example.com

for(memeber of indexContact) {
  console.log(memeber);
}
 
/* 以下出力
{name_0: "Foo", email_0: "foo@example.com"}
{name_1: "Bar", email_1: "bar@example.com"}
{name_2: "Boo", email_2: "boo@example.com"} 
*/

メソッド定義の短縮構文

ES6 からメソッド定義の function 句を使わない構文が導入されています。

//ES5 までのメソッドの定義
var obj = {
  foo: function() {
    console.log('foo');
  },
  bar: function() {
    console.log('bar');
  }
};

上記は以下のように function を省略して記述することができます。

//ES6 からは function を省略可能
const obj = {
  foo() {
    console.log('foo');
  },
  bar() {
    console.log('bar');
  }
};

算出プロパティ(算出されたキー)も使用することができます。

const x = 'foo', y = 'bar';
const obj = {
  [x + y] () {
    console.log('FooBar');
  },
};

obj.foobar();  //FooBar