JavaScript ES6(ECMAScript 2015)で追加された機能
以下は ES6(ECMAScript 2015)で追加された機能(let、const、テンプレートリテラル、デフォルト引数、Rest パラメータ、スプレッド構文、分割代入、イテレータ、for..of、Array.from()、アロー関数、モジュール、Symbol、Map、Set、クラス構文、ジェネレータ)の基本的な使い方についての覚書です。
Code Point を扱うメソッドやサロゲートペアを使った文字の扱いなども追加しました。
ブラウザの対応状況
参考になるサイト:
関連ページ:
作成日:2020年5月6日
変数の宣言
ES6(ECMAScript 2015)から新たに変数の宣言方法として let と const が導入されました。
従来からある var の代わりにこれらのキーワードを使うことで変数を二重に定義してしまうなどのミスを防ぐことができます。
キーワード | 説明 |
---|---|
let | ブロックスコープの局所的な変数を宣言。値を再代入することはできますが、もう一度宣言(再宣言)することはできません。 |
const | ブロックスコープの局所的な変数を宣言しますが、再代入による変更はできず、再宣言もできません。宣言時に値(初期値)を設定する必要があります。 |
変数の宣言を行うキーワードは var、const、let の3つになり、ほとんどの場合、var は const または let に置き換えることができます。
var には同じ名前の変数を再定義できてしまうなどの問題があるため、変数を宣言する場合、const を使って定義できないかを検討し、できない場合は let を使うことが推奨されています。
また、トップレベルで var を使って宣言した変数は window のプロパティになりますが、const と let ではなりません(window オブジェクトのプロパティを生成しません)。
var foo = 'Foo'; console.log(window.foo); //Foo const bar = 'Bar'; console.log(window.bar); //undefined let baz = 'Baz'; console.log(window.baz); //undefined
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
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
テンプレートリテラル
テンプレートリテラル(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}
引数
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
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"]
スプレッド構文
スプレッド構文 ...(変数名の前にピリオド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 */ //既存の項目を新しい配列にコピーしてから新しい項目を末尾に追加 const more_fruits = [...fruits, 'melon', 'painapple']; console.log(more_fruits); /* 出力結果 ["apple", "banana", "orange", "melon", "painapple"] */
以下は既存のオブジェクトをスプレッド構文を使って拡張する例です。
※オブジェクトでのスプレッド構文は 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 obj_ext2 = { ...obj_ext, //option3 の値を上書き option3 : 'huga' } console.log(obj_ext2); //{option1: "foo", option2: "bar", option3: "huga"}
同名プロパティは上書きされる
上記の最後の例のように、同名のプロパティが存在するオブジェクト内では後に書いたプロパティで上書きされます。
const obj1 = { a: 'foo', b: 'bar', c: 'baz' }; const obj2 = { ...obj1, c: 'qux'}; // c は上書きされる console.log(obj2); //{a: 'foo', b: 'bar', c: 'qux'}
オブジェクトを複製(コピー)
前述の例のようにオブジェクトの浅いコピーを簡単に作成することができます。
const foo = { num: 123 }; //コピーを作成 const bar = { ...foo }; console.log(bar.num); //123 bar.num = 456; //コピーを変更 console.log(bar.num); //456 console.log(foo.num); //123 ※ foo は変更されない
但し、スプレッド構文でネストしたオブジェクトをコピーする場合は注意が必要です。
スプレッド構文は、「各プロパティが同じ値を持つ新しいオブジェクトを作成する」ので、プロパティの中にオブジェクトが入っている場合、それは同じオブジェクトのままです。
const foo = { obj: { name: 'foo' } }; //コピーを作成 const bar = { ...foo }; console.log(bar.obj.name); //'foo' bar.obj.name = 'bar'; //コピーのオブジェクトを変更 console.log(bar.obj.name); //'bar' console.log(foo.obj.name); //'bar' ※ foo も変更される
文字列を配列に
以下はスプレッド構文を使って文字列を配列にする例です。
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] arr2.push(4); //arr2 に要素を追加 console.log('arr2: ', arr2); //arr2: [1, 2, 3, 4] console.log('arr: ', arr); //arr: [1, 2, 3] arr は変更されない
スプレッド構文を使って多次元配列を複製する場合は注意が必要です。コピーは1段階の深さで行われます。
以下の場合、複製された arr2 の要素の配列を変更すると、arr の要素の配列も変更されます(同じ要素を参照しています)。
let arr = [ [1, 2, 3], ['a', 'b'] ]; let arr2 = [...arr]; arr2[0].push(4); //arr2[0] に要素を追加 console.log(arr2[0]); // [1, 2, 3, 4] console.log(arr[0]); // [1, 2, 3, 4] arr[0] も変更される
配列を連結
スプレッド構文を使うと簡単に配列を連結したり結合することができます。
順序を入れ替えたり、要素を追加するなどが簡単に行なえます。
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"]
配列のようなオブジェクトから配列を生成
スプレッド構文を使って、配列のようなオブジェクトから配列を生成することができます。
以下は getElementsByTagName() で取得した配列のようなオブジェクト(HTMLCollection)を配列に変換して、配列のメソッド forEach() を使う例です。Array.from() を使っても同様のことができます。
//配列のようなオブジェクトを配列に変換 const divsArray = [...document.getElementsByTagName('div')]; divsArray.forEach(function(elem) { if(elem.id) { //取得した div 要素に id が付与されていれば出力 console.log(elem.id); } });
分割代入
分割代入構文を使うと、配列やオブジェクトから値を取り出して複数の変数に代入することができます。
通常の代入と同じく代入演算子(=)を使いますが、左辺のオペランドが配列リテラルやオブジェクトリテラルとなります。
配列
以下は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
引数
関数の引数にも分割代入が利用できます。
代入演算子(=)を使う場合は、左辺のオペランドがオブジェクトリテラルで、右辺にオブジェクトを指定しますが、引数の場合は、仮引数がオブジェクトリテラルで、関数に渡す引数にオブジェクトを指定します。
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); }
反復可能オブジェクト(iterable)
反復可能オブジェクト(iterable object)とはイテレータ(iterator)を持つオブジェクトのことで、イテレータによりオブジェクトの要素(プロパティ)を順番に取り出すことができるオブジェクトです。
イテレータはデータ構造に対して反復処理によって各要素を返す機能を持つオブジェクトです。
反復可能オブジェクトは Symbol.iterator をキーとするメソッド(@@iterator)を持ち、そのメソッドを実行するとイテレータを返す仕組みになっています。
大雑把に言うと、反復可能オブジェクトとは反復処理(繰り返し処理)時に各要素を返す動作が定義されたオブジェクトになります。
組み込み型(ビルトイン)オブジェクトでは配列や文字列、Map、Set などが該当します。
反復可能オブジェクトと配列のようなオブジェクト
「反復可能オブジェクト(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 というオブジェクトを返します。
プロパティ | 説明 |
---|---|
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 関数。この関数には以下の引数が与えられます。
|
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 ・・・*/
以下は、length プロパティのみを持つ配列のようなオブジェクトから、インデックスの値を要素に持つ新しい配列を生成する例です。
const myArray = Array.from({length: 5}, (v, i) => i) ; //または const myArray = Array.from({length: 5}).map((v, i) => i); でも同じ console.log(myArray); // [0, 1, 2, 3, 4]
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
Array.prototype.find()
find() メソッドは配列から条件に合う「最初の要素」の「値」を返します。
以下が書式です。
arr.find(callback(element, index, array), thisArg)
引数 | 説明 |
---|---|
callback | 配列内の各要素に対して実行する(条件に合致した時に真偽値を返す)関数。次の3つの引数を取ります。
|
thisArg | (省略可能)callback 内で this として使われるオブジェクト。callback の呼び出しのたびに、その内部で this の値として使用され、この引数を省略した場合は undefined が使用されます。 |
返り値
callback 関数で条件に合致する「最初の要素」の値。見つからなかった場合は undefined を返します。
以下は配列 arr の中で8より大きい「最初の要素」の「値」を取得する例です。
const arr = [3, 12, 37, 8, 9, 22]; const found = arr.find(function(elem) { return elem > 8; }); console.log(found); //12
通常はアロー関数を使って以下のように記述します。
const arr = [3, 12, 37, 8, 9, 22]; const found = arr.find(elem => elem > 8); console.log(found); //12
条件に合致する要素の配列を返す関数 filter() を使うと以下のように書き換えることができます。
filter() は条件に合致する要素から成る新たな配列を返すのに対し、find()メソッドは条件に合致する「最初の要素」の「値」を返します。
const arr = [3, 12, 37, 8, 9, 22]; //filter() は条件に合致する「要素の配列」を返す const found = arr.filter(elem => elem > 8)[0]; //最初の要素 console.log(found); //12
条件に合致する要素の添字(インデックス)を取得する場合は、Array.prototype.findIndex() を使用します。使い方は find() とほぼ同じですが、見つからなかった場合は -1 を返します。
const arr = [3, 12, 37, 8, 9, 22]; //findIndex() は条件に合致する最初の要素の添字を返す const foundIndex = arr.findIndex(elem => elem > 8); console.log(foundIndex); //1
以下はオブジェクトの配列から条件に合致する最初のものを取得する例です。
const myMembers = [ { id: '001', name: 'foo', age: 22 }, { id: '002', name: 'bar', age: 36 }, { id: '003', name: 'baz', age: 57 }, ]; // id プロパティの値が の要素 const id002 = myMembers.find(member => member.id === '002'); console.log(id002.name); //bar // age プロパティの値が 50 より大きい要素 const over50 = myMembers.find(member => member.age >= 50); console.log(over50.name); // baz
Array.prototype.findIndex()
findIndex() メソッドは配列から条件に合う「最初の要素」の「位置」を返します。使い方は find() とほぼ同じです。
以下が書式です。
arr.findIndex(callback(element, index, array), thisArg)
引数 | 説明 |
---|---|
callback | 配列内の各要素に対して実行する(条件に合致した時に真偽値を返す)関数。次の3つの引数を取ります。
|
thisArg | (省略可能)callback 内で this として使われるオブジェクト。 |
返り値
callback 関数で条件に合致する「最初の要素」の「位置(添字:インデックス)」。見つからなかった場合は -1 を返します。
以下は配列 fruits の中で値が「pineapple」の最初の要素の位置(インデックス)を取得する例です。
const fruits = ["apple", "banana", "cherry", "pineapple"]; const index = fruits.findIndex(fruit => fruit === "pineapple"); console.log(index); //3 console.log(fruits[index]); //pineapple
上記の場合は indexOf() を使って以下のように記述できます。
const fruits = ["apple", "banana", "cherry", "pineapple"]; const index = fruits.indexOf("pineapple"); console.log(index); //3 console.log(fruits[index]); //pineapple
以下は配列 fruits の中で値の文字数が5より大きい最初の要素の位置(インデックス)を取得する例です。
const fruits = ["apple", "banana", "cherry", "pineapple"]; const index = fruits.findIndex(fruit => fruit.length > 5); console.log(index); //1 console.log(fruits[index]); //banana
以下は querySelectorAll() で li 要素の集まり(NodeList)を取得して、Array.from() で配列に変換し、class 属性の値が foo の最初の要素の位置(インデックス)を取得する例です。要素の class 属性の値は className で取得できます。
<ul id="myList"> <li>apple</li> <li>banana</li> <li class="foo">cherry</li> <li>pineapple</li> </ul> <script> //li 要素の集まり(NodeList)を配列に変換 const listItemsArray = Array.from(document.querySelectorAll('#myList li')); //class 属性の値が foo の最初の要素の位置(インデックス)を取得 const index = listItemsArray.findIndex(item => item.className ==='foo'); console.log(index); //2 console.log(listItemsArray[index].textContent); //cherry </script>
アロー関数
アロー関数は関数リテラル(関数式)を簡潔に記述する代替構文です。
アロー関数には以下のような特徴があります。
- 簡潔に記述できる
- 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; }; //上記はブロック {} と return を省略して記述可能(簡潔文体) const multiply = (x, y) => x * y; // 簡潔構文の場合、暗黙の return があります //但し、以下は期待通りに動かない const multiply = (x, y) => { x * y; }; //ブロック文体では自動的に return されないので明示的に return する必要がある console.log(multiply(2,3)) //undefined |
引数が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"]
オブジェクトリテラルの返却
params => {object:literal}
のような省略形(concise body syntax)を使ってオブジェクトリテラルを返そうとしても、中括弧 { }
内のコードは一連のステートメント(文)として解析されてしまうため、期待通りに動作しません(以下の場合、foo はラベルであり、オブジェクトのキーではありません)。
//期待通りに動作しない(オブジェクトとして return できない) const func = () => { foo: 123 } console.log(func()); // undefined
省略形(concise body syntax)を使ってオブジェクトリテラルを返す場合は、括弧 ( )
で囲みます。
//括弧 () で囲むことでオブジェクトとして return const func = () => ({ foo: 123 }) console.log(func()); // {foo: 123} //または以下でもOK const func2 = () => { return {foo: 123} } console.log(func2()); // {foo: 123}
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 要素のテキストが表示される }); });
モジュール
ES6(ECMAScript 2015)では JavaScript プログラムをモジュールに分割して管理する仕組みが導入されました。
モジュールとは一般的に別のプログラムに取り込むことを前提としたある機能をひとまとめにした(関数やコンポーネントなどの機能ごとに分けた)ファイルのことです。
caniuse JavaScript modules via script tag
モジュールは単なる1つのファイルで、1つの JavaScript ファイルに対応します。
export 文を使用することで変数や関数などをエクスポートすることができ、エクスポートした変数や関数などは import 文を使用してインポートして(読み込んで)利用できます(export / import)。
HTML ファイルでの JavaScript ファイルの読み込みや JavaScript の実行では <script> と書けば通常のスクリプトとして実行され、<script type="module">と書けばモジュールとして実行されます。
※ モジュールが動作するには MAMP などのローカル環境が必要です。直接 HTML ファイルを開くと same Origin Policy により、JavaScript モジュールが正しく動作しません。
読み込まれる側(モジュール側)
以下はモジュールとしての外部 JavaScript ファイル(module_01.js)の例です。
モジュールの JavaScript ファイルでは export 文を使ってアクセス可能な変数や関数などを定義します。必要に応じて import を使って別のモジュールから変数や関数などをインポートすることもできます。
export で定義されたり、宣言されたものだけが他の JavaScript ファイルから参照可能になります。
//export で関数 showMessage を定義 export function showMessage(message) { console.log(message); }
読み込む側
以下は HTML ファイルでモジュールを import を使って読み込む例です。
<script> 要素に type="module" を指定する必要があります。
import の from でモジュールのファイルを指定します。その際、「./」や「../」を使ってパスを指定する必要があります。※ 同じディレクトリ(階層)にある場合でも、「./」は省略できません。
この例の場合は HTML とモジュールは同じ階層にあるので「./」と指定しています。
<!-- HTML で script タグで読み込み(type="module" を指定)--> <script type="module"> // import で関数 showMessage を module_01.js からインポート import {showMessage} from "./module_01.js"; // インポートした関数を実行 showMessage('Hello Module!'); </script>
src 属性で外部ファイルを指定することもできます。
例えば、以下のような外部ファイル(imports.js)を作成し、HTML ファイルで読み込むことができます。
// 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 文は、モジュールの中でのみ使うことができます。通常のスクリプトの中では使えません。
参考:JavaScript Primer エントリーポイント
モジュールの特徴
モジュールとして読み込まれるソースコードは以下のような特徴があります。
- 常に 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 から機能の名前を指定してインポートする例です。
//名前を指定してインポート 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 のデフォルトエクスポートをインポートする例です。
//デフォルトエクスポート export default function myFunc(val) { console.log(val); }
//デフォルトインポート import myFunc from './module_02.js'; //インポートした関数を実行 myFunc('bar');
上記の例では、インポートする際の名前をデフォルトエクスポートの関数名と同じにしていますが、以下のように任意の名前を指定できます。
import foo from './module_02.js'; //指定した名前でインポートした関数を呼び出す foo('bar'); //myFunc() が実行される
エイリアス
import 文や export 文の { } の中で、キーワード as と新しい名前(別名/エイリアス)を指定することで、その機能を使うときの名前を変更することができます。
異なる名前でエクスポート
エイリアスを使うと、宣言済みの変数や関数などを異なる名前でエクスポートできます。以下のように as の後にエクスポートしたい名前(エイリアス)を指定します。
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 の後に別名(エイリアス)を指定します。
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 の全てのエクスポートを取得する例です。
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']
名前の衝突の回避
例えば、以下のように同じ名前を持つ関数を異なるモジュールからインポートすると名前の衝突によるエラーが発生します。
const foo = 'foo'; let bar = [1, 2, 3]; function myFunc(val) { console.log('x:' + val); } export { foo, bar, myFunc };
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 を使う例です。
//test という関数をエクスポートするファイル(モジュール) export const test = function() { console.log('ログ出力'); }
//test という関数をインポートして使用するファイル test.html <script type="module"> import { test } from './module.js' test(); //ログ出力(コンソールに出力される) </script>
以下は同様なことを require を使って行う例です(Node.js がインストールされている必要があります)。
//module.exports を使って関数を定義(ES6 の export に対応) module.exports = function() { console.log('ログ出力'); }
エントリーポイントとなるファイルで require を使ってモジュールを読み込みます。
//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 にしたモジュールを読み込む例です。
export const test = function() { console.log('ログ出力'); }
import { test } from './module.mjs' test();
ターミナルで node コマンドを実行するとモジュールの関数が実行されます。
$ node main.mjs ログ出力 //ターミナル(標準出力)での表示
- MDN export
- MDN import
- javascript.info モジュール, 導入
- javascript.info エクスポートとインポート
- jsprimer.net [ES2015] ECMAScriptモジュール
- thinkit.co.jp ES2015のモジュール管理
import() dynamic import
dynamic import は ES2020 で導入された動的に import を実行するための機能で、以下が構文です。
import() は関数呼び出しに似た構文ですが、 import 自体はキーワードで関数ではありません。
import(モジュール名)
import() を使うと指定されたモジュールを動的に非同期で読み込むことができます。
普通の import 宣言では、import する側のモジュール(コード)が実行される前に import されたモジュールが読み込まれて実行されますが、dynamic import では import() が実行されるまでモジュールの読み込みが行われません。
このため、遅延読み込みをしたい(モジュールが必要になるときまでモジュールを読み込みたくない)場合などに使用することができます。
また、普通の import 宣言と異なり、トップレベルでなくても使用でき、モジュール以外の環境(スクリプト)でもモジュールをインポートすることができます。
import() は Promise を返す
import() にモジュールのパスを渡すと、そのモジュールの名前空間オブジェクト(モジュールのすべてのエクスポートを格納したオブジェクト)を結果として持つ Promise を返します。
言い換えると、import() は読み込んだモジュールの全てのエクスポートを Promise でラップして返します。
例えば以下のようなモジュールをインポートする場合、
export const foo = 'foo'; export const bar = 3; export default () => console.log("Default Hello");
import() は Promise を返すので、Promise の then メソッドを使えば、コールバック関数の引数に名前空間オブジェクトが渡されます。この場合、script タグに type="module" は必要ありません。
<script> // response に名前空間オブジェクトが渡される import('./module.js').then((response) => { console.log(response.foo, response.bar); // foo 3 response.default(); // Default Hello }); </script>
名前付きエクスポートに対しては、以下のようにオブジェクトの分割代入が使えます。
import('./module.js').then(({foo, bar}) => { console.log(foo, bar); // foo 3 });
import() での読み込みは非同期処理として扱われるので、以下を実行すると先に「同期的処理」が出力され、その後に「foo 3」が出力されます。
import('./module.js').then(({foo, bar}) => { console.log(foo, bar); }); console.log('同期的処理'); // 同期的処理 // foo 3
then の代わりに async と await を使って以下のように記述することもできます。
(async () => { const response = await import('./module.js'); console.log(response.foo, response.bar); // foo 3 response.default(); // Default Hello })();
ファイルがモジュールであればトップレベル await が使えるので以下のように記述することができます。
<script type="module"> // ファイルがモジュールの場合(スクリプトの場合はエラーになる) const response = await import('./module.js'); console.log(response.foo, response.bar); // foo 3 response.default(); // Default Hello </script>
トップレベルでなくても使用可能
import() はトップレベルでなくても使用できるので、例えば以下のように条件文の中で使用したり、関数の中で使用することもできます。
const flag = true; if (flag) { import('./module.js').then(({ foo, bar }) => { console.log(foo, bar); }); }
また、import() に指定するモジュールのパスは変数で指定することもできます。
const url = './module.js'; import(url).then(({ foo, bar }) => { console.log(foo, bar); });
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 | |
---|---|---|
キーの型 | あらゆる値を指定可能 (関数、オブジェクト、全てのプリミティブなど) | 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 | それぞれの要素に対して実行するコールバック関数。以下の引数が渡されます。
|
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 */
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 との互換性のために作られていて、セットにはキーは存在しないので要素の値が入っています。
|
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 */
クラス構文
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() {} と書いたのと同じように動作します。
定義できるのはメソッドのみ
クラス直下の本体部分で定義できるのはメソッドのみです。インスタンスのプロパティはクラスのコンストラクタやメソッドの中で定義しなければなりません。
ES2022 ではクラスフィールド構文が導入され、プロパティもクラス直下で定義できるようになっています。
プロトタイプメソッド
class ブロック内でコンストラクタとは別に関数を定義するとプロトタイプメソッドを宣言したことになります。プロトタイプメソッドとは、クラスのインスタンスから呼び出せる(プロトタイプに設定された)メソッドです。
インスタンスメソッド
コンストラクタの中で定義した関数をインスタンスメソッドと呼びます。インスタンスメソッドもクラスのインスタンスから呼び出せるメソッドです(関連:インスタンスメソッド)。
プロパティ
オブジェクトリテラルとは異なり、class ブロック内で property : value の定義はできません。クラスにプロパティ(インスタンス変数)を持たせる場合は、コンストラクタ内で記述します。または、getter や setter メソッド(アクセッサプロパティ)を使うことで対応することができます。
※(追記)ES2022 で class ブロック内で property : value のように定義できるようになりました。
インスタンスのプロパティ(インスタンス変数)を定義したり参照するには、this.変数(プロパティ名) とします。this.変数 で定義される変数はプロパティ名としてアクセスすることができます。
prototype にプロパティを設定することも可能ですが、その場合、全てのオブジェクト(インスタンス)に対してプロパティを追加することになり非効率になります。
以下は class 構文を使ったシンプルなクラス Person の例です。
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
ES2022 クラスフィールド
ES2022で、クラスのインスタンスが持つプロパティの初期化をわかりやすく宣言的にするクラスフィールド構文が追加されました。
クラスにプロパティを持たせる場合、コンストラクタ内で記述する必要がありましたが、ES2022 ではプロパティをクラスブロック直下に変数として定義することができます。
また、クラスに対して宣言されるプロパティ(クラスブロック直下に変数として定義するプロパティ)はフィールドと呼びます。
以下がクラスフィールドの構文です。
クラスフィールドで定義したプロパティは、他のプロパティ同様 this.プロパティ名 で参照できます。
class クラス { プロパティ名 = プロパティの初期値; }
先述の class 構文はクラスフィールド構文を使って以下のように記述することができます。
class Person { // プロパティの宣言(初期値は省略可能) name = 'no name'; // クラスフィールド constructor(name) { //引数の値があればその値でプロパティを初期化 if(name) { this.name = name; } } hello() { alert('My name is ' + this.name); } }
JavaScript Primer: [ES2022] Publicクラスフィールド
メソッドの定義
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 Name)。
[ ] の中では変数や式、シンボルなどを使うことができます。
const val1 = 'foo', val2 = 'bar'; const obj = { [val1 + val2] : 'BOO' //[ ] の中の式が展開(算出)されて foobar : '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
ジェネレータ
Generator は ジェネレータ関数によって生成される標準ビルトインオブジェクト(組み込みオブジェクト)で、反復処理プロトコル(iterable/iterator)を実装しています。
言い換えると、ジェネレータは反復可能オブジェクトであり、且つイテレータでもあるオブジェクトです。
ジェネレータは以下のメソッドを持ちます。
メソッド | 説明 |
---|---|
next() | yield 式によって得られる値(yield 式の評価結果)とジェネレータが完了したかどうかを表す真偽値を IteratorResult オブジェクトで返します(※)。 |
return() | 与えられた値を返し、ジェネレータを終了します |
throw() | エラーをジェネレータにスローします |
※ next() はイテレータ(iterator)が実装するメソッドで、value と done という2つのプロパティを持つ IteratorResult というオブジェクトを返します。
基本的な使い方としては、ジェネレータ関数を呼び出してジェネレータのインスタンスを生成し、そのメソッド next() を使ってジェネレータ関数の実行を制御します。
ジェネレータ関数
ジェネレータ関数は特殊な関数で、通常の関数が単一の値しか返さない(または何も返さない)のに対して、ジェネレータ関数は実行を断続的に中止しながら複数の値を返す(生み出す)ことができます。
別の言い方をすると、ジェネレータ関数は処理を抜け出すことも後から復帰することもできる関数です。
また、ジェネレータ関数は呼び出されると、関数は実行されず関数の実行を制御するための特別なオブジェクトであるジェネレータ(Generator)を生成して返します。
ジェネレータ関数を定義するには特別な構文 function* を使用します。function キーワードのあとに *(アスタリスク)が付き、このように宣言された関数はジェネレータ関数となります。
以下はジェネレータ関数を宣言する構文です。
- params(オプション):関数に渡される引数名。引数は255個までもつことが可能
- statements:関数本体に含まれる命令文
function* ジェネレータ関数名([params]) { statements }
ジェネレータ関数は呼び出されると、処理は実行されずジェネレータオブジェクトを生成して返します。
function* myGenerator() { yield 'foo'; yield 'bar'; } //ジェネレータ関数を呼び出して変数に代入 const generator = myGenerator(); //コンソールへ出力 console.log(generator); /*出力結果(停止中の Generator オブジェクトであることがわかる) myGenerator {<suspended>} __proto__: Generator */
yield
yield キーワードは、ジェネレータ関数を一時停止したり再開したりするために使用します。
yield はジェネレータ関数の実行を一時停止し、ジェネレータの呼び出し元に yield キーワードに続く値を戻します。
yield はジェネレータ関数の中でしか呼び出すことができません。
以下が yield の構文です。
- expression:ジェネレータ関数が返す値。省略した場合は undefined が返されます。
- rv:next() メソッドに渡した値を受け取ります(もしあれば)。
[rv] = yield [expression]
yield キーワードはジェネレータの next() メソッドを呼び出させ、value と done の2つのプロパティを持つ IteratorResult オブジェクトを返します。 value プロパティは yield 式の評価結果(値)で、 done プロパティはジェネレータが完了したかどうかを表す真偽値です。
yield 式によって実行が停止されると、再びジェネレータの next() メソッドが呼び出されるまで、ジェネレータのコードの実行は一時停止します。
別の言い方をすると、イテレータでもあるジェネレータの next() メソッドを使って、ジェネレータ関数の yield 式が返す値を取得することができます。
next() メソッドの呼び出しで、ジェネレータは yield に達するまでコードを実行します。yield に達するとその時点での結果(value と done という2つのプロパティを持つオブジェクト)を返して実行を中断(suspend)します。
ジェネレータ関数のコードの実行は最初の部分で一時停止されていて、next() が呼ばれると最も近い yield に達するまでコードを実行し、その後、実行は一時停止(suspend)します。
next() メソッドは value と done という2つのプロパティを持つオブジェクト(IteratorResult)を返し、value にはその時点での値が、done にはジェネレータが完了したかどうかの真偽値が含まれています。
function* myGenerator() { yield 1; yield 2; } //ジェネレータを生成 const generator = myGenerator(); //ジェネレータの next() メソッドを呼び出して実行 const next1 = generator.next(); // yield 1 までが実行され value: 1, done: false のオブジェクトが返されて中断 console.log(next1); //出力 {value: 1, done: false}
以下の場合、1回目の next() の呼び出しでは yield 1 までを実行し、{value: 1, done: false} が返され、その後一時停止します。
再び next() を呼び出すと実行が再開し、2回目の next() の呼び出しでは yield 2 までを実行し、{value: 2, done: false} が返されます。
3回目の next() では yield がもうないので {value: undefined, done: true} が返されます。値(value)は undefined で、done: true になりジェネレータが完了したことを示しています。
もし、更に next() を呼び出しても、{value: undefined, done: true} が返されるので意味がありません。
function* myGenerator() { yield 1; yield 2; } //ジェネレータを生成 const generator = myGenerator(); //next() を呼び出して実行(1回目) const next1 = generator.next(); // yield 1 までが実行され value: 1, done: false のオブジェクトが返されて中断 console.log(next1); //出力 {value: 1, done: false} console.log(next1.value); //1 (value プロパティを出力) console.log(next1.done); //false (done プロパティを出力) // next() を再度呼び出して実行(2回目) const next2 = generator.next(); // yield 2 までが実行され value: 2, done: false のオブジェクトが返されて中断 console.log(next2); //出力 {value: 2, done: false} // next() を再度呼び出して実行 (3回目) const next3 = generator.next(); // yield がもうないので value: undefined, done: true のオブジェクトが返されて中断 console.log(next3); //出力 {value: undefined, done: true}
以下は next() の戻り値の value プロパティのみを出力する例です。
function* myGenerator() { yield 1; yield 2; } //ジェネレータを生成 const generator = myGenerator(); //1回目の next() console.log(generator.next().value); // 1 //2回目の next() console.log(generator.next().value); // 2 //3回目の next() console.log(generator.next().value); // undefined
以下のように最後に return 文がある場合は、3回目を呼び出すと実行は return 文に到達して、return で指定された値 "end" と done プロパティが true のオブジェクトが返され、ジェネレータが完了します。
更に next() を呼び出しても、{value: undefined, done: true} が返されます。
function* myGenerator() { yield 1; yield 2; //関数を終了する return 文 return 'end'; } //ジェネレータを生成 const generator = myGenerator(); //1回目の next() const next1 = generator.next(); console.log(next1); //{value: 1, done: false} //2回目の next() const next2 = generator.next(); console.log(next2); //{value: 2, done: false} //3回目の next() const next3 = generator.next(); console.log(next3); //{value: "end", done: true}
以下はジェネレータを生成して、next() を実行すると、それぞれの呼び出しで引数で指定した値の加算、減算、乗算、除算を行うジェネレータ関数の例です。
function* myGenerator(x, y) { yield x + y; yield x - y; yield x * y; if(y !==0) { yield x / y; } else { yield 'error'; } } const gen = myGenerator(5, 7); console.log(gen.next().value); //12 console.log(gen.next().value);; //-2 console.log(gen.next().value);; //35 console.log(gen.next().value);; //0.7142857142857143
一時停止
今までの例では yield で値を返していましたが、値は返さずに単に実行を一時停止するような使い方もできます。
以下は、ボタンをクリックするとイベントリスナーにより next() メソッドを呼び出してコンソールに「カウント開始」と出力して、タイマーでカウントアップを開始し、yield の位置で関数の実行を一時停止します(タイマーはそのままカウントアップを続けます)。
2回目にボタンをクリックすると、再び next() メソッドを呼び出してコンソールに「終了するにはもう一度クリックしてください」と出力し、次の yield の位置で関数の実行を一時停止します。
3回目にボタンをクリックすると、再び next() メソッドを呼び出してタイマーをクリアしてその時点でのカウントとメッセージを出力し、return で関数を終了します。その後、ボタンをクリックしても何も起きません。
<button id="btn">Click</button> <script> function* generateOutput(){ let count = 0; this.timerID = setInterval( () => count++, 1000 ); console.log('カウント開始'); yield; console.log('終了するにはもう一度クリックしてください'); yield; clearInterval(this.timerID); console.log('終了 Count: ' + count); return; } //ジェネレータを生成 const gen = generateOutput(); //ボタン要素を取得 const btn = document.getElementById('btn'); //イベントリスナーを登録(ボタンをクリックすると next() を呼び出す) btn.addEventListener('click',()=> gen.next()); </script>
ジェネレータに値を渡す
ジェネレータ関数を再開する際に、next(arg) のように引数を持つ next() メソッドを呼び出して関数に値を渡すことができます。
この引数は yield の結果になります。
function* myGenerator(){ const a = yield 'start'; const b = yield a; yield b; } const gen = myGenerator(); // yield 'start' で "start" が返される console.log(gen.next()); //{value: "start", done: false} //a(yield 'start' の結果)は foo に置き換わり、yield a が実行される console.log(gen.next('foo')); //{value: "foo", done: false} //b(yield a の結果)は bar に置き換わり、yield b が実行される console.log(gen.next('bar')); //{value: "bar", done: false} // yield がもうないので完了 console.log(gen.next()); //{value: undefined, done: true}
以下は myGenerator() の引数に渡された値と next() の引数の値を使って計算して値を返す例です。
最初の next() の呼び出しでは対応する yield がないので、引数を指定していません。
2回目の next() の呼び出しで渡された引数(以下の例では5)が1回目の yield の返り値(変数 a に格納された値)になります。
function* myGenerator(value){ //最初の引数なしの next() では myGenerator() の引数の値を表示 const a = yield `引数の値は ${value}`; //2回目の next() では next() の引数の値(a)を使って計算 const b = yield `${a} の${value}倍は ${a * value}`; //3回目の next() では next() の引数の値(b)を使って計算 yield `${b} の${a}倍は ${b * a}`; } const gen = myGenerator(3); const result1 = gen.next(); console.log(result1.value); //引数の値は 3 const result2 = gen.next(5); console.log(result2.value); //5 の3倍は 15 const result3 = gen.next(35); console.log(result3.value); //35 の5倍は 175
以下のジェネレータ関数は next() メソッドに渡された値を加算していき、next() メソッドが呼ばれるとその時点までの合計を返すものです。
この例の場合、while (true) としているので、無限に next() メソッドを呼び出すことができます。
function* sumUp(){ let sum = 0; while (true){ //x は next() の引数の値に置き換わる const x = yield sum; console.log('x: ' + x + ' sum: ' + sum); sum += x; } } const gen = sumUp(); // 関数の初回実行時の sum は 0 gen.next(); //next() の引数に渡された値を加算 console.log(gen.next(5).value); //5 console.log(gen.next(7).value); //12 console.log(gen.next(99).value); //111 //コンソールへの出力結果 x: 5 sum: 0 //初回の加算時 5 //17行目の出力(加算結果) x: 7 sum: 5 //2回目の加算時 12 //18行目の出力(加算結果) x: 99 sum: 12 //3回目の加算時 111 //19行目の出力(加算結果)
ジェネレータは反復可能
ジェネレータは反復可能オブジェクト/iterableなので for...of 文で値を取り出すことができます。
next().value を呼び出して値を取得するよりも簡潔に記述できます。
function* myGenerator() { yield 1; yield 2; yield 3; } const generator = myGenerator(); //for...of 文でジェネレータの値を取り出す for(let value of generator) { console.log(value); } //出力 1 2 3
ジェネレータが次の値を持っているか(done プロパティ)
上記の for...of 文は、以下の iteratorResult オブジェクトの done プロパティを判定する while 文と同じ(シンタックスシュガー)です。
ジェネレータが次の値を持っているかどうかを知るには、結果として取得できる iteratorResult オブジェクトの done プロパティで調べることができます。
function* myGenerator() { yield 1; yield 2; yield 3; } const generator = myGenerator(); //next()が返す iteratorResult オブジェクトを格納する変数 let result; //iteratorResult オブジェクトの done プロパティが false の間繰り返し while (!(result = generator.next()).done) { console.log(result.value); }
但し、for..of 文のイテレーションは done: true のとき、最後の value を無視するので注意が必要です(上記 while 文からわかるように done: false の場合のみ value を処理)。
以下の場合、return 文で返された値は出力されません。すべての結果を for..of 文で取得したい場合は、それらを yield で返す必要があります。
function* myGenerator() { yield 1; yield 2; // return 文(for..of では取得できない) return 3; } const generator = myGenerator(); for(let value of generator) { console.log(value); } //出力(3は出力されない) 1 2
反復可能オブジェクトを生成
オブジェクトを反復可能(iterable)にするには、Symbol.iterator メソッドを実装する必要があります。
以下は独自のオブジェクトにイテレータの next() メソッドを使って Symbol.iterator メソッドを実装する例です(詳細:Symbol.iterator の例)
//オブジェクトを作成 var obj = {foo: 'FOO', bar: 'BAR', boo: 'BOO'}; // オブジェクトを iterable に(Symbol.iterator を実装) obj[Symbol.iterator] = function(){ var iterator = {}; var current = 0; var keys = Object.keys(this); //イテレータの next() メソッドを定義 iterator.next = () => { var iteratorResult = (current < keys.length) ? { value: this[keys[current]], done: false } : { value: undefined, done: true }; current++; return iteratorResult; }; return iterator; };
以下はジェネレータ関数を使って、オブジェクトを反復可能(iterable)にする例です。
ジェネレータは next() メソッドを持ち、{value: 値, done: true/false} の形式で値を返すので、上記と同じことをより簡潔に記述することができます。
反復可能オブジェクトでは、スプレッド構文や for...of 文を使うことができます。
//オブジェクトを作成 var obj = {foo: 'FOO', bar: 'BAR', boo: 'BOO'}; // Symbol.iterator をジェネレータ関数を使って実装 obj[Symbol.iterator] = function*(){ //プロパティの名前(キー)の配列 var keys = Object.keys(this); //オブジェクトの要素(プロパティ)の数は上記で取得した配列の length for(let i=0; i<keys.length; i++) { //オブジェクトの値はキーから取得して yield で返す yield this[keys[i]]; } } //スプレッド構文を使って展開できる console.log(...obj); //FOO BAR BOO //for...of 文を使って値を1つずつ取り出して繰り返し処理できる for(var value of obj) { console.log(value); } /* FOO BAR BOO */
シーケンスの作成
ジェネレータは IteratorResult オブジェクトを返す next() メソッドを持つイテレータ(iterator)であり、且つ反復可能オブジェクト(iterable)でもあるので、ジェネレータを使えば、簡単に反復可能なシーケンスを作ることができます。
以下は、引数に指定した start から end までの連続した整数を発生させる(ジェネレータオブジェクトを生成する)ジェネレータ関数の例です。
// start から end までの連続した整数を発生させるジェネレータ関数 function* generateSequence(start, end) { for (let i = start; i <= end; i++) { yield i; } } //スプレッド構文を使って展開できる let sequence = [...generateSequence(7,11)]; console.log(sequence); // [7, 8, 9, 10, 11] //for...of 文を使って値を1つずつ取り出して繰り返し処理できる for (const i of generateSequence(3,6)){ console.log(i); } /* 3 4 5 6 */
以下は、引数に指定した start から end までの整数で、引数 by に指定された値で割り切ることができる整数を生成するジェネレータです。
引数を指定しなければデフォルト値(by=2, start=1, end=30)が適用されます(デフォルト引数)。
//by で指定された値で割り切れる整数を生成するジェネレータ function* generateDivisibleSequence(by=2, start=1, end=30) { let num = start; while (num <= end) { if (num % by === 0) { yield num; } num++; } } let sequence1 = [...generateDivisibleSequence()]; console.log(sequence1); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30] let sequence2 = [...generateDivisibleSequence(7, 20, 50)]; console.log(sequence2); //[21, 28, 35, 42, 49]
yield* ジェネレータの合成
yield* 式は別のジェネレーターや反復可能なオブジェクトに実行を委任するために使用します。
以下が yield* の構文です。
- expression:反復可能なオブジェクト(iterable)を返す式
yield* expression;
yeild* を使うことで別のジェネレータや反復可能なオブジェクト(イテレータ)から値を生成することができます。つまり、別のジェネレータを埋め込む(合成する)ことができます。
function* myGenerator1() { yield 'foo'; yield 'bar'; yield 'buz'; } function* myGenerator2() { yield 'HELLO!'; //myGenerator1 を埋め込み(合成) yield* myGenerator1(); yield 'BYE!'; } const gen = myGenerator2(); console.log(gen.next()); // {value: "HELLO!", done: false} console.log(gen.next()); // {value: "foo", done: false} console.log(gen.next()); // {value: "bar", done: false} console.log(gen.next()); // {value: "buz", done: false} console.log(gen.next()); // {value: "BYE!", done: false} console.log(gen.next()); // {value: undefined, done: true}
ジェネレータオブジェクト以外にも配列や文字列、arguments オブジェクトなどの反復可能なオブジェクトを yield することができます。
function* mixGenerator() { yield* [1, 2]; yield* 'bar'; yield* Array.from(arguments); } const gen = mixGenerator('foo'); console.log(gen.next()); // {value: 1, done: false} console.log(gen.next()); // {value: 2, done: false} console.log(gen.next()); // {value: "b", done: false} console.log(gen.next()); // {value: "a", done: false} console.log(gen.next()); // {value: "r", done: false} console.log(gen.next()); // {{value: "foo", done: false} console.log(gen.next()); // {value: undefined, done: true}
yield* 式自体の値
yield* は式であり、そのため値に評価されます。yield* の返り値はイテレータが done:true を返したときの value の値です。
function* gntr1() { yield* [1, 2]; return 'foo'; } function* gntr2() { const returnValue = yield* gntr1(); console.log(returnValue) // 'foo'(done:true の時の value の値) return returnValue; } const gen = gntr2(); console.log(gen.next()); // {value: 1, done: false} console.log(gen.next()); // {value: 2, done: false} // foo (8行目の出力) console.log(gen.next()); // {value: "foo", done: true} console.log(gen.next()); // {value: undefined, done: true}
上記ジェネレータ関数 gntr1() の return を yield に変更すると、yield* gntr1() の返り値は undefined になります(イテレータが done:true を返したときの value の値は undefined になるため)。
function* gntr1() { yield* [1, 2]; // return を yield に変更 yield 'foo'; } function* gntr2() { const returnValue = yield* gntr1(); console.log(returnValue) // undefined(done:true の時の value の値) return returnValue; } const gen = gntr2(); console.log(gen.next()); // {value: 1, done: false} console.log(gen.next()); // {value: 2, done: false} console.log(gen.next()); // {value: "foo", done: false} // undefined console.log(gen.next()); // {value: undefined, done: true}
return()
ジェネレータは next() メソッド以外に、return() と throw() というメソッドを持っています。
return() メソッドは与えられた値を返し、ジェネレータを終了するメソッドです。
return() を実行すると、引数に与えられた値が value として返され、done: true となりジェネレータが終了します。
function* gntr() { yield 1; yield 2; yield 3; } const gen = gntr(); console.log( gen.next() ); // { value: 1, done: false } //return() を実行してジェネレータを終了(引数に与えられた値が value として返される) console.log( gen.return("foo") ); // { value: "foo", done: true } console.log( gen.next() ); // { value: undefined, done: true }
throw() 例外
エラー(例外)をジェネレータにスローする(渡す)には throw() メソッドを使います。
throw() を呼び出すと、エラーが yield に渡されます(yield のある行に投げられます)。
以下の場合、throw() を呼び出して実行した時点(2回目の console.log)でエラーがジェネレータ関数内の yield に渡されて例外が発生し、2回目以降の console.log は実行されません(スクリプトは強制終了します)。
function* gntr() { yield 1; yield 2; yield 3; } const gen = gntr(); console.log( gen.next() ); // { value: 1, done: false } //throw() を実行して例外を発生させる console.log( gen.throw(new Error("例外発生!")) ); // Uncaught Error: 例外発生! console.log( gen.next() ); //実行されない
エラーは try...catch ブロックによって捕捉することができます。
以下の場合、エラーが発生すると catch ブロックによって捕捉されます。
function* gntr() { try { yield 1; yield 2; yield 3; }catch(e){ console.log(e); // エラーを捕捉してコンソールへ出力 } } const gen = gntr(); console.log( gen.next() ); // { value: 1, done: false } //throw() を実行して例外を発生させる console.log( gen.throw(new Error("例外発生!")) ); // Error: 例外発生! {value: undefined, done: true} console.log( gen.next() ); //{value: undefined, done: true}
以下のように呼び出し側のコードでもエラーを捕捉することができます。
function* gntr() { yield 1; yield 2; yield 3; } const gen = gntr(); console.log( gen.next() ); // { value: 1, done: false } //呼び出し側のコードでもエラーを捕捉することができます try { console.log( gen.throw(new Error("例外発生!")) ); } catch(e) { console.log(e); // エラーを捕捉してコンソールへ出力(Error: 例外発生!) } console.log( gen.next() ); //{value: undefined, done: true}
String 関連
ES6(ECMAScript 2015)ではいくつかの String 関連のメソッドが追加されています。
文字列の検索
指定された文字列が含まれているかを検索する以下のような文字列のメソッドが追加されています。
メソッド | 説明 |
---|---|
String.prototype.includes() | 指定された文字列を含むかを判定して真偽値を返します |
String.prototype.startsWith() | 指定された文字列が先頭にあるかを判定して真偽値を返します |
String.prototype.endsWith() | 指定された文字列が末尾にあるかを判定して真偽値を返します |
includes()
includes() は、実行する文字列の中に引数で指定した文字列が含まれるかを判定して真偽値を返します。
str.includes(searchString[, position])
引数 | 説明 |
---|---|
searchString | str 内で検索される文字列(検索値) |
position | searchString を検索し始める位置。既定値は 0(省略可能) |
返り値
文字列が検索値を含む場合は true。含まなければ、false。
//検索対象の(メソッドを実行する)文字列 const sentence = 'JavaScript is not Java'; //Script は含まれるので true console.log(sentence.includes('Script')); //true //検索開始位置を指定 console.log(sentence.includes('Script',4)); //true console.log(sentence.includes('Script',5)); //false //大文字・小文字を区別するので以下は false console.log(sentence.includes('java')); //false const search = 'Java'; console.log(`「${search}」は「${sentence}」に${sentence.includes(search) ? '含まれます' : '含まれません'} `); //「Java」 は 「JavaScript is not Java」 に含まれます
startsWith()
startsWith() は、実行する文字列が引数で指定した文字列で始まるかを判定して真偽値を返します。
str.startsWith(searchString[, position])
引数 | 説明 |
---|---|
searchString | str 内の先頭で検索される文字列(検索値) |
position | searchString を検索し始める位置。既定値は 0(省略可能) |
返り値
文字列が指定された文字列で始まる場合場合は true。そうでなければ、false。
//検索対象の(メソッドを実行する)文字列 const sentence = 'JavaScript is not Java'; console.log(sentence.startsWith('Java')); //true //検索開始位置を指定 console.log(sentence.startsWith('Java',1)); //false //後半の Java にマッチ console.log(sentence.startsWith('Java',18)); //true
endsWith()
endsWith() は、実行する文字列が引数で指定した文字列で終わるかを判定して真偽値を返します。
str.endsWith(searchString[, length])
引数 | 説明 |
---|---|
searchString | str 内の末尾で検索される文字列(検索値) |
length | str の長さを指定することができます。既定値は str.length(省略可能) |
返り値
文字列が指定された文字列で終わる場合場合は true。そうでなければ、false。
//検索対象の(メソッドを実行する)文字列 const sentence = 'JavaScript is not Java'; console.log(sentence.endsWith('Java')); //true //検索される文字列(sentence)の長さを指定 console.log(sentence.endsWith('Java',18)); //false //最初の Java にマッチ console.log(sentence.endsWith('Java',4)); //true
Unicode (Code Point)関連
文字コードとは文字をコンピュータで処理したりするためにそれぞれの文字に番号を割り振ったもので、Unicode や Shift JIS、EUC など様々な文字コードがあります。
JavaScript は文字コードとして Unicode を、エンコード方式として UTF-16 を採用しています。以下は JavaScript Primer の「文字列とUnicode」 からの抜粋です。
JavaScriptは文字コードとしてUnicodeを採用し、エンコード方式としてUTF-16を採用しています。 このUTF-16を採用しているのは、あくまでJavaScriptの内部で文字列を扱う際の文字コード(内部コード)です。 そのため、コードを書いたファイル自体の文字コード(外部コード)は、UTF-8のようにUTF-16以外の文字コードであっても問題ありません。
以下も JavaScript Primer 文字列 からの抜粋です。
-
JavaScript の文字列の構成要素は UTF-16 で変換された Code Unit(符号単位)です
-
JavaScript 文字列は16ビットの Code Unit が順番に並んだものとして内部的に管理されています。
用語 | 説明 |
---|---|
符号化 | 文字からビット列へ変換すること(エンコード) |
符号空間 | Unicode で文字の符号化のために使用できる整数の範囲。 0 から 0x10FFFF |
Code Point 符号位置 |
Unicode で規定されている文字1つ1つに割り振られている数値(ID のようなもの)。符号空間に属する値で文字コード表のマス目の位置を表します。 |
Code Unit 符号単位 |
エンコードされたテキストの1文字を表すのに使う最小限のビットの組み合わせ。UTF-16 の場合、各 Code Unit のサイズは16ビット(2バイト) |
基本多言語面 BMP |
Basic Multilingual Plane の略。基本的な文字・記号のほとんどが含まれる最初の65536の符号位置である U+0000~U+FFFF(16ビットで表せる範囲内の文字)のコード領域。この領域のコードポイントを「BMP 符号位置」と呼ぶこともあるみたい 。 |
追加多言語面 SMP |
Supplementary Multilingual Plane の略。絵文字や古代文字など、BMP に入らなかった文字を入れるコード領域(U+010000~U+10FFFF) |
方式 | 説明(概要) |
---|---|
UTF-8 | 8ビットの可変長マルチバイトで文字を表現。符号位置の値によって長さが1 ~ 4バイトに変化する可変長の符号化方式。 |
UTF-16 | 16ビットの可変長マルチバイトで文字を表現。符号位置を16ビットで表現する2または4バイトの可変長の符号化方式。U+0000 ~ U+FFFFの範囲ならそのまま16ビットデータとして、U+10000 ~ U+10FFFFの符号位置(コードポイント)はサロゲートペアを使って表現する符号化方式。 ★ JavaScript の内部で文字列を扱う際に使用されるエンコード方式 |
UTF-32 | 32ビットの固定長マルチバイトで文字を表現。常に32ビットで符号位置を表現する固定長の符号化方式。 |
エスケープシーケンス | 説明 |
---|---|
\uXXXX | \u とその文字を表す Code Unit(符号単位)の4桁の16進数(hex 値)を指定します。 |
\u{XXXXXX} | \u と { } 内にその文字を表す Code Point(符号位置)の16進数(hex 値)を指定します。※ ES6(ECMAScript 2015) で導入。 |
Code Point と Code Unit
Code Point(符号位置)は Unicode で規定されている1つ1つの文字に割り振られている一意のID(数値)です。
Code Unit(符号単位)はエンコードされたテキストの1文字を表すのに使う最小限のビットの組み合わせで、UTF-16 の場合、各 Code Unit のサイズは16ビット(2バイト)になっていて、これが JavaScript の文字列の構成要素です。
ある範囲の文字列(基本的な文字・記号など)については、1つの符号位置に1文字が割り当てられているので、Code Point(符号位置)とCode Unit(符号単位)は同じ値になっています。
但し、Unicode では特殊な文字や絵文字などを2つの符号位置を組み合わせて1文字として表示するようになっているため(サロゲートペア)、JavaScript ではそれらの文字を表すのに、2つの Code Unit(符号単位)を使いますが、それらの文字にも一意の Code Point(符号位置)が割当てられているため、それらの文字では Code Point(符号位置)とCode Unit(符号単位)の値が異なります。
以下は同じ絵文字(😊)を Code Unit(符号単位)と Code Point(符号位置)で指定する例で、2つの Code Unit で1つの Code Point を表現しているのがわかります。
\u の後が Code Unit(符号単位)で \u の後の { } 内が Code Point(符号位置)です。
//2つの Code Unit console.log("\uD83D\uDE0A"); //😊 //1つの Code Point console.log("\u{1F60A}"); //😊
サロゲートペア
Unicode では1文字あたり2バイトを使いますが、Unicode に組み込む文字が増えたため、従来の2バイト(65536文字)では文字が足りなくなり、解決策として「1文字=2バイト」の基本は従来通りで、一部の文字についてサロゲートペア(Surrogate pair)という「1文字=4バイト」にする方法が導入されました。
サロゲートペアは、従来の Unicode では未使用だった 0xD800~0xDBFF を「上位サロゲート」、0xDC00~0xDFFF を「下位サロゲート」と規定し、「上位サロゲート+下位サロゲート」の組み合わせの4バイトで文字を表現します。
上位サロゲート | 0xD800~0xDBFF(1024通り) |
下位サロゲート | 0xDC00~0xDFFF(1024通り) |
サロゲートペアでは、2つの Code Unit の組み合わせ(4バイト)で1つの文字(1つの Code Point)を表現するようになっています。
前述の例の絵文字(😊)は 0xD83D(上位サロゲート)と 0xDE0A(下位サロゲート)の組み合わせで1つの 0x1F60A(Code Point)を表しています。
以下は JavaScript では文字列が UTF-16 の Code Unit(符号単位)で内部的に管理されていることを示す例です。
String の length プロパティ
String オブジェクトの length プロパティは文字列長を Code Unit の数で表します。そのため、絵文字などでは長さは2になります。
const str = "😊"; console.log(str.length); //2
絵文字などサロゲートペアを使った文字列を正しくカウントする方法は文字列を分割・カウントを参照ください。
charCodeAt() メソッド
charCodeAt() メソッドは、指定された位置にある文字の Code Unit を返します。
const str = "😊"; for (let i = 0; i < str.length; ++i) { //Code Unit を取得して hex 値(16進数)に変換 console.log(str.charCodeAt(i).toString(16)); } //d83d //de0a
split() メソッド
split() メソッドは指定した区切り文字列で配列に分割しますが、区切り文字列に空文字を指定した場合、Code Unit ごとに分割されます。
通常の文字列の場合は1もじごとに分割されますが、絵文字などの場合は期待通りに分割されません(サロゲートペアが分割されてしまいます)。
const splitArray = "😊は絵文字".split(""); console.log(splitArray); // ["\ud83d", "\ude0a", "は", "絵", "文", "字"]
絵文字などサロゲートペアを使った文字列を正しく分割する方法は文字列を分割・カウントを参照ください。
Code Point を扱うメソッド
ECMAScript 2015(ES6)では Code Point を扱うメソッドが追加されています。
メソッド | 説明 |
---|---|
String.prototype.codePointAt() | 文字列の指定されたインデックスにある文字の Code Point の値を返します |
String.fromCodePoint() | String の静的なメソッドで、指定された Code Point に対応する文字を返します。 |
codePointAt()
codePointAt() は文字列の指定されたインデックスにある文字の Code Point の値を返すメソッドです。
str.codePointAt(pos)
引数 | 説明 |
---|---|
pos | str の中の対象の要素の位置 |
返り値
指定された pos の位置にある Code Point の値を表す数値。pos の位置に要素がない場合は undefined。
以下は「あ」と「😊」の Code Point と Code Unit の値をそれぞれ codePointAt() と charCodeAt() で取得しています。
// "あ" の Code Point の値 console.log("あ".codePointAt(0)); //12354 // "あ" の Code Unit の値 (この場合は Code Point の値と同じ) console.log("あ".charCodeAt(0)); //12354 // "😊" の Code Point の値 console.log("😊".codePointAt(0)); //128522 // "😊" の Code Unit の値 (この場合は Code Point の値と異なる) console.log("😊".charCodeAt(0)); //55357
16進数に変換するには、toString() の引数に16を渡します。
// "あ" の Code Point の16進数の値 console.log("あ".codePointAt(0).toString(16)); //3042 // "あ" の Code Unit の16進数の値 (この場合は Code Point の値と同じ) console.log("あ".charCodeAt(0).toString(16)); //3042 // "😊" の Code Point の16進数の値 console.log("😊".codePointAt(0).toString(16)); //1f60a // "😊" の Code Unit の16進数の値 (この場合は Code Point の値と異なる) console.log("😊".charCodeAt(0).toString(16)); //d83d
以下の str は5文字ですが、最初の絵文字(😊)は2つの Code Unit で構成されているため、length プロパティは6となります。
const str = "😊は絵文字"; //length を出力 console.log(str.length); //6 //codePointAt() でそれぞれの Code Point を出力 for (let i = 0; i < str.length; ++i) { console.log(str.codePointAt(i).toString(16)); } //1f60a (😊 の Code Point) //de0a (😊 の 下位サロゲートの Code Point) //306f (は の Code Point) //7d75 (絵 の Code Point) //6587 (文 の Code Point) //5b57 (字 の Code Point) //charCodeAt() でそれぞれの Code Unit を出力 for (let i = 0; i < str.length; ++i) { console.log(str.charCodeAt(i).toString(16)); } //d83d (😊 の Code Unit) //de0a (😊 の 下位サロゲートの Code Unit) ※Code Point と同じ //306f (は の Code Unit) ※Code Point と同じ //7d75 (絵 の Code Unit) ※Code Point と同じ //6587 (文 の Code Unit) ※Code Point と同じ //5b57 (字 の Code Unit) ※Code Point と同じ
fromCodePoint()
fromCodePoint() は指定された Code Point に対応する文字を返します。
fromCodePoint() は String の静的なメソッドなので、文字列(String オブジェクト)のメソッドとしてではなく、常に String.fromCodePoint() として使用します。
String.fromCodePoint(num1[, ...[, numN]])
引数 | 説明 |
---|---|
num1, ..., numN | Unicode の Code Point の数値(10進数または16進数)。任意の数だけ指定可能。 |
返り値
指定された Code Point を使って生成された文字列。
例外
無効な Code Point が与えられた場合、RangeError がスローされます。
以下は fromCodePoint() を使った例です。
//"あ"の Code Point(10進数)を取得 console.log("あ".codePointAt(0)); //12354 //Code Point(10進数)から文字(あ)を生成 console.log(String.fromCodePoint(12354)); //あ //"あ"の Code Point を16進数に変換 console.log("あ".codePointAt(0).toString(16)); //3042 //16進数の Code Point から文字(あ)を生成 console.log(String.fromCodePoint(0x3042)); //あ //fromCodePoint には複数の Code Point を指定可能 console.log(String.fromCodePoint(12354, 12356, 12358)); //あいう console.log(String.fromCodePoint(0x3042, 0x3044, 0x3046)); //あいう console.log(String.fromCodePoint(0x1f60a, 0x306f, 0x7d75, 0x6587, 0x5b57)); //😊は絵文字 //無効な Code Point を指定(エラーが発生) String.fromCodePoint(-1); //Uncaught RangeError: Invalid code point -1
文字列を分割・カウント
split() メソッドや length プロパティは Code Unit を基準に処理されるため、絵文字などのサロゲートペアを使った文字列に対してはうまく機能しませんが、ECMAScript 2015(ES6)で導入された Array.from メソッドやスプレッド構文、for...of 文などを使うことで Code Point を基準に分割することができます。
Array.from で Code Point ごとに区切る
Array.from メソッドを使うと、Code Point ごとに区切った配列へと変換することができます。
const str = "😊は絵文字"; //文字列から Code Point ごとに区切った1文字ずつの配列を生成 const strArray = Array.from(str); console.log(strArray); // ["😊", "は", "絵", "文", "字"] console.log(strArray.length); // 5
スプレッド構文で Code Point ごとに区切る
スプレッド構文を使うと、Code Point ごとに区切った配列へと変換することができます。
const str = "😊は絵文字"; //配列に文字列の要素を展開 const strArray = [...str]; console.log(strArray); // ["😊", "は", "絵", "文", "字"] console.log(strArray.length); // 5
for...of 文で Code Point ごとに反復処理
for...of 文を使うと、Code Point ごとに反復処理することができます。
const str = "😊は絵文字"; const strArray = []; for (let char of str) { strArray.push(char); } console.log(strArray); // ["😊", "は", "絵", "文", "字"] console.log(strArray.length); // 5
match() メソッドを使用
以下は正規表現の match() メソッドを使って文字列を分割する例です。
[\uD800-\uDBFF][\uDC00-\uDFFF] はサロゲートペアを表すパターンで、[\s\S] は全ての文字を表す文字クラスです。
また、str に空文字列を渡すと、match() は null を返すので、その場合は空の配列 [] を返しています。
const str = "😊は絵文字"; //match() メソッドを使用して文字列を分割 const strArray = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S]/g) || []; console.log(strArray); // ["😊", "は", "絵", "文", "字"] console.log(strArray.length); // 5
正規表現 u フラグ
ECMAScript 2015(ES6)では u フラグ(unicode オプション)が導入されています。u フラグを指定することで、Unicode のサロゲートペアを使った4バイト文字も1文字として扱うことができます。
以下は正規表現の match() メソッドで u フラグを使わない場合の例ですが、絵文字を正しく処理することができません。
match() メソッドの戻り値は配列で、最初の要素にはマッチした文字列全体が入り、それ以降はマッチした配列内のカッコの付きのサブ表現のテキストが入ります。
以下の場合、(.) は😊の下位サロゲート(\ude0a)にマッチしていて期待通りにはなりません。
const str = "😊は絵文字"; const result = str.match(/(.)は絵文字/); console.log(result[0]); //�は絵文字(マッチした文字列全体) console.log(result[1]); //� (カッコの付きのサブ表現のテキスト) console.log(result.length); //2 console.log(result); // ["\ude0aは絵文字", "\ude0a", index: 1, input: "😊は絵文字", groups: undefined]
u フラグを指定することで、サロゲートペアを使った4バイト文字も正しく処理されます。
const str = "😊は絵文字"; const result = str.match(/(.)は絵文字/u); console.log(result[0]); //😊は絵文字 console.log(result[1]); //😊 console.log(result.length); //2 console.log(result); // ["😊は絵文字", "😊", index: 0, input: "😊は絵文字", groups: undefined]
参考サイト: