ECMAScript 2015の新機能(その2)

2023/01/14更新

目次

概要

2015年に公開された新しいJavaScriptの標準仕様であるECMAScript 2015(ES6、ES2015)では、JavaScriptを便利にする数多くの新機能が盛り込まれた。それらをひと通りまとめておく。

かなりの分量となったため、2つの記事に分けている。また、型付き配列、非同期処理関連、モジュールはさらに別ページでまとめた。

クラス関連

クラス構文

従来、JavaScriptにはクラスを記述するための専用の構文は存在せず、クラス定義をする場合は、prototypeを使うなどして、以下のように記述していた。

// クラス定義&コンストラクタ
function MyClass(a1, a2, …) {
    // プロパティ
    this.prop1 = a1;
      :
};
// インスタンスメソッド
MyClass.prototype.myMethod = function(b1, b2, …) {
    console.log(this.prop1);
      :
};
// クラスメソッド
MyClass.myStaticMethod(c1, c2, …) {
      :
};

これが、ES2015のクラス構文を使うと、概ね以下のように書けるようになる。

// クラス定義(「class」キーワードを使う)
class MyClass {
    // コンストラクタ(「constructor」という名前で定義する)
    constructor(a1, a2, …) {
        // プロパティ
        this.prop1 = a1;
          :
    }
    // インスタンスメソッド
    myMethod(b1, b2, …) {
        console.log(this.prop1);
          :
    }
    // クラスメソッド(「static」キーワードを使う)
    static myStaticMethod(c1, c2, ...) {
          :
    }
}

アクセサプロパティ

クラス構文内で、アクセサプロパティ(セッターとゲッターを通してアクセスするプロパティ)が簡単に定義できる。代入時の値チェックや加工などに使える。

class MyClass {
    constructor() {
        this._myValue = 0;
    }
    // セッター
    set myValue(x) {
        this._myValue = Math.floor(x);
    }
    // ゲッター
    get myValue() {
        return `${this._myValue}円`;
    }
}
const obj = new MyClass();

// 通常のプロパティ「_myValue」に直接代入・取得した場合
obj._myValue = 123.456;
console.log(obj._myValue); // 123.456

// 定義したアクセサプロパティ「myValue」を通して代入・取得した場合
obj.myValue = 123.456;
console.log(obj._myValue); // 123
console.log(obj.myValue);  // '123円'

セッター・ゲッター内で他のセッターやゲッターを使うことはできるが、セッター内で自分自身への代入文を書いたり、ゲッター内で自分自身を取得しようとしたりすると、無限ループとなってしまうので注意。また、セッターとゲッターは通常両方とも必要で、片方が無いと代入できない、または取得できないプロパティとなってしまう。

クラスの継承

extendsで他のクラスの継承が簡単にできる。

// 親クラス
class MyClass {
    constructor(x) {
        this.myValue = x;
    }
    getMyValue() {
        return this.myValue;
    }
}
// MyClassを継承した子クラス
class MyChildClass extends MyClass {
    constructor(x, y) {
        // 親クラスのコンストラクタの実行
        super(x);
        // 親クラスのメソッドの実行
        const val = super.getMyValue();
        this.myChildValue = val + y;
    }
    getMyChildValue() {
        return this.myChildValue;
    }
}
const obj = new MyChildClass(111, 222);
console.log(obj.getMyValue());      // 111
console.log(obj.getMyChildValue()); // 333

非同期処理関連

非同期処理関連の機能については、別記事「Promiseによる非同期処理」にまとめた。

MapとSet

Map

Mapは、連想配列を実現するための新しいオブジェクトである。JavaScriptにおける連想配列は従来オブジェクトが使われてきたが、連想配列のサイズをすぐに知ることができない、キーに文字列しか使えない、継承したプロパティとキーが衝突する、などの問題点があった。

そこで、連想配列としての機能に特化した新しいオブジェクトがMapである。ただ、Mapにはリテラル表記が無かったり、JSONとの相性が悪かったりするなど、従来のオブジェクトを使った連想配列を完全に置き換えるものとなっているわけではない。

// 空のMapを生成
const map1 = new Map();

// エントリー(キー/値ペアの配列)からMapを生成
const map2 = new Map([['hoge', 123], ['fuga', 456]]);

// Mapのサイズ
console.log(map1.size); // 0
console.log(map2.size); // 2

// 値の取得
console.log(map2.get('hoge')); // 123

// 値の追加・上書き
map2.set('fuga', 789);
console.log(map2.get('fuga')); // 789

// キーの存在確認
console.log(map2.has('hoge')); // true

// キーの削除
map2.delete('hoge');
console.log(map2.has('hoge')); // false

// 全てのキーの削除
map2.clear();
console.log(map2.size); // 0

Mapはiterableなオブジェクトであるため、オブジェクトではできなかったfor-ofによる列挙が簡単にできる。

const map = new Map([ ~ ]);

// for-ofによる列挙
for (const key of map.keys()) {
    console.log(`key=${key}`);
}
for (const val of map.values()) {
    console.log(`value=${val}`);
}
for (const [key, val] of map) {
    console.log(`key=${key}, value=${val}`);
}

// forEachによる列挙
map.forEach((val, key) => {
    console.log(`key=${key}, value=${val}`);
});

Mapのキーには任意のオブジェクトが使用できる。キーが同じであるかの判定は、===による判定とほぼ同じであるが、NaNだけは例外で、NaN === NaNfalseであるにもかかわらず、Mapでは同じキーと見なされる。なお、0-0は同じキーと見なされるため、Object.is()による比較とも異なる。

Set

Setは、一意かつ順序を持たない集合を実現するための新しいオブジェクトである。連想配列のキーだけを使っているとイメージすれば分かりやすい。配列などの要素の重複の除去などに利用できる。

// 空のSetを作成
const set1 = new Set();

// iterableなオブジェクトからSetを作成
const set2 = new Set([1, 2, 2, 1, 3]); // {1, 2, 3}
const set3 = new Set('fizzbuzz');      // {'f', 'i', 'z', 'b', 'u'}

// Setのサイズ
console.log(set1.size); // 0
console.log(set2.size); // 3

// 値の追加
set2.add(4);
console.log(set2.size); // 4
set2.add(3);
console.log(set2.size); // 4 ※既に存在する値は追加されない。

// 値の存在確認
console.log(set2.has(4)); // true
console.log(set2.has(5)); // false

// 値の削除
set2.delete(1);
console.log(set2.has(1)); // false

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

Setはiterableなオブジェクトであるため、Mapと同様にfor-ofで列挙できる。なお、列挙する際は、挿入した順に取り出される。

const set = new Set([ ~ ]);

// for-ofによる列挙
for (const val of set) {
    console.log(val);
}

// forEachによる列挙
set.forEach(val => {
    console.log(val);
});

WeakMapとWeakSet

WeakMapは、キーとして参照型のオブジェクトのみを使用できるMapである。このとき、キーは「弱い参照」で保持されるため、そのオブジェクトへの参照が無くなったとき、そのキーへは到達不可能と見なされ、GCによって自動的に削除される可能性がある。WeakSetも同様で、参照型のオブジェクトのみを「弱い参照」で保持できるSetである。

// WeakMapの作成
const wmap = new WeakMap();

// オブジェクトをキーにして値をセット
const obj = {};
wmap.set(obj, 123);

// キーにしたオブジェクトへの参照があるうちは、存在確認や値の取得ができる。
console.log(wmap.has(obj)); // true
console.log(wmap.get(obj)); // 123

// オブジェクトへの参照が無くなると、格納した値「123」にアクセスする手段が無くなるため、GCによってWeakMapから自動的に削除される。
obj = null;

WeakMapWeakSetはiterableではないため、for-ofなどによる列挙はできない。また、sizeでサイズを知ることもできない。

モジュール

モジュールについては、別記事「JavaScriptモジュール(ES modules)」にまとめた。

その他

Symbol

Symbol()によって生成される新しい種類の値。以下のような性質を持つ。

  • Symbol()によって作った値をtypeof演算子にかけるとsymbolとなる。

  • Symbol()によって作った値は、自分自身以外とは決して等しくならない。後から同じものを再度生成することはできない。

  • オブジェクトのプロパティとして使用することができ、しかもfor-in文で列挙ができない。

必ず一意であるという性質と、オブジェクトのプロパティに使用できるという性質によって、他と衝突しないようにオブジェクトを密かに拡張したい場合などに使える。実際、iterableなオブジェクトが実装している[Symbol.iterator]プロパティなどがそれである。

Symbol()関数には、Symbol('hoge')のように、文字列を渡してもよい。この文字列は単にコード上で意味を分かりやすくするためだけのもので、生成されるSymbolに対しては特に意味は無い。同じ文字列を渡しても、同じSymbolとはならない。

プロキシ

未稿。

関連記事

外部リンク