今回はECMAScript2015(ES2015)で追加、拡張された標準ライブラリの機能について紹介します。
Unicode
新しいUnicodeリテラルが追加されました。これにより、サロゲートペア対象の文字列でも分割せずに表記できるようになりました。
また、正規表現ではUnicodeフラグをつけることで、サロゲートペアを分割せずに1文字として処理することができます。
リスト2:Unicodeフラグをつけると、サロゲートペアを1文字と処理できる
Stringのイテレータはサロゲートペアを考慮してくれるため、下記の記述が可能です。
リスト3:StringのイテレータはUnicodeを正しく認識する
01 | const fishes = "\u{29E49}\u{29E3d}" |
04 | for (let fish of fishes){ |
効率的なデータ構造
データのコレクションを扱うための標準ライブラリが追加されました。
Map
単純なKey-Valueの値を扱うMapが追加されました。JavaScriptではKey-Value形式のデータを扱う場合、一般的にオブジェクトを使用してきました。ES2015で追加されたMapを使用することで、keyに任意の値をもたせることができたり、Mapのデータを扱うための便利なメソッドを使用できるようになりました。
リスト4:Key-Value型式のデータを扱うMap
MapオブジェクトやvaluesメソッドはIterableなため、for..ofやSpreadオペレーターを使用できます。
リスト5:MapオブジェクトはIterable
1 | for ( var [key, val] of map){ |
2 | console.log(`key = ${key}, value = ${val}`) |
7 | let arr = [...map.values()] |
Set
Setは一意の値を格納できる集合を表すコレクションライブラリです。格納される値は一意となるため、重複している値を追加しようとしてもSetに値は追加されません。
リスト6:一意の値を格納するSet
2 | let story = { 'title' : 'Alice in Wonderland' } |
WeakMap
WeakMapは任意のオブジェクトをKeyにもつMapです。MapではKeyに任意のデータを持たせることができましたが、WeakMapではオブジェクトのみである点に注意してください。
リスト7:WeakMapのKeyはオブジェクトのみ
WeakMapではKeyへの参照が弱参照となるため、keyのオブジェクトが外部から参照されなくなった場合にガベージコレクションの対象となります。この仕様を利用することで、メモリ効率を考慮したオブジェクトの保存(例えばキャッシュなど)を実現することができます。
リスト8:WeakMapの仕様を利用したコード例
04 | this .userCache = new WeakMap() |
08 | return this .userCache.get(ID) |
12 | this .userCache.set(ID, cache) |
WeakSet
WeakSetは任意のオブジェクトを格納するコレクションオブジェクトです。Setでは任意の値を追加できましたが、WeakSetの場合、追加できるのはオブジェクトのみとなります。
前述したWeakMapと同様にオブジェクトへの参照は弱参照となるため、格納されているオブジェクトの参照がない場合はガベージコレクションの対象となります。
リスト9:WeakSetを用いたコードの例
1 | let weakset = new WeakSet(); |
2 | let story = { 'title' : 'Alice in Wonderland' } |
Proxies
Proxyを使用すると、オブジェクトに対しての基本的な動作(プロパティの参照、代入、列挙、関数の実行など)について独自に挙動を追加で定義することができます。
下記の例ではsetメソッドを定義することで、オブジェクトへの値代入時のValidationとして使用しています。
リスト10:Proxyの機能を利用したコードの例
02 | set: function (target, prop, value, receiver){ |
03 | if (prop === 'age' && !( typeof value === 'number' )){ |
04 | throw new TypeError( '年齢は数字で入力してください。' ) |
11 | let person = new Proxy(target, validation) |
Symbols
ES2015で追加されたSymbolは、ユニークなデータを保持するプリミティブ型のオブジェクトです。作成されたSymbolオブジェクトは一意な値となります。
リスト11:Symbolオブジェクト
1 | let sym = Symbol( 'name' ) |
6 | console.log(obj[ 'name' ]) |
Symbolが一意な値になる性質を利用することで、列挙型にも活用することができます。
リスト12:Symbolを列挙型として利用する
2 | DEBUG: Symbol( 'debug' ), |
複数のファイルをまたいでSymbolを使用する場合は、グローバルSymbolレジストリを使用する必要があります。
リスト13:複数ファイル間でSymbolを使用する
1 | let globalSymbol = Symbol. for ( 'global' ) |
2 | console.log(Symbol.keyFor(globalSymbol)) |
Well-known Symbols
Symbolは、言語内部のオブジェクトのふるまいをカスタマイズするWell-known Symbolsを持っています。前回の記事で説明した「Iterators + For..Of」に登場したSymbol.iteratorもWell-known Symbolsの一つです。
ES2015で導入された、より洗練された構文 Part 2 - Iterators + For..Of
Well-known Symbolsを使用することで、iteratorを持っていないオブジェクトに対して独自のiteratorを追加することもできます。
リスト14:独自のiteratorを追加する
01 | let heroine = {name: 'Alice' , age: 7} |
03 | heroine[Symbol.iterator] = () => { |
06 | keys = Object.keys(heroine) |
08 | iterator.next = () => { |
09 | let done = !(i < keys.length), |
10 | value = heroine[keys[i]] |
12 | return done ? { done } : {value, done} |
17 | for (status of heroine){ |
その他のWell-known Symbolsについては、ECMAScript 2015 Language Specificationの「6.1.5.1 Well-Known Symbols」を参照してください。
ECMAScript® 2015 Language Specification - 6.1.5.1 Well-Known Symbols
Subclassable Built-ins
ES2015ではArrayやDate、DOM Elementsなどを継承し、サブクラスを作成することができるようになりました。
リスト15:Arrayを継承したサブクラスの作成
01 | class MyArray extends Array{ |
03 | return this [Math.floor(Math.random() * this .length)] |
07 | let arr = new MyArray(1, 2, 5, 9) |
既存の標準ライブラリ(Math/Number/String/Object/Array)に追加された新機能
ES2015では既存の標準ライブラリに対しても新しい機能が追加されています。
Math
数学的な定義や関数を定義するMathオブジェクトに対して、対数や三角関数などの数学関数や、ビット演算に関連するスタティックメソッドが追加されました。
対数関連のメソッド
常用対数(10を底とする対数)や2を底とする対数を返すメソッドなどが追加されています。
リスト16:追加された対数関連のメソッド
7 | e(自然対数の底であるネイピア数)のx乗から-1した値を返します |
三角関数関連のメソッド
双曲線関数、逆双曲線関数を返すメソッドが追加されています。
算術関連のメソッド
立方根や、与えられた値の符号(正、負、ゼロ)を返すメソッドなどが追加されています。
ビット計算関連のメソッド
Number
数値を扱うNumberオブジェクトに対して、新たなプロパティやメソッドが追加されました。
追加されたプロパティ
EPSILON(ε)は、Numberオブジェクトで表現できる最小の正数を表します。
追加されたメソッド
リスト21:追加されたNumberに関するメソッド
03 | Number.isFinite(Infinity) |
07 | Number.isInteger(Math.PI) |
16 | Number.parseFloat( '123Daah!' ) |
18 | Number.parseFloat( 'route53' ) |
23 | Number.parseInt( '1000' , 2) |
String
String.prototype.includes
対象の文字列の中に特定の文字列が存在するかどうかを判別します。検索する文字列が含まれていた場合はtrueを、含まれていなかった場合はfalseを返します。
リスト22:Stringに追加されたincludesメソッド
1 | let alankay = 'The best way to predict the future is to invent it.' |
2 | alankay.includes( 'The best' , 0) |
3 | alankay.includes( 'The best' , 1) |
String.prototype.repeat
与えられた文字数を指定された回数だけ繰り返して新しい文字列を作成します。
リスト23:Stringに追加されたrepeatメソッド
1 | let res = 'FizzBuzz' .repeat(3) |
Object
Object.assign
ターゲットオブジェクトに対して、一つ以上のオブジェクトをコピーすることができます。ES5まではオブジェクトを=で代入した場合は参照渡しとなってしまい、オブジェクトのコピーができませんでした。そのためオブジェクトをコピーするためにjQueryの$.extend()などを使用していました。
ES2015で追加されたObject.assignでは、オブジェクトがターゲットオブジェクトにコピーされるため、ライブラリなどを使用しなくとも意図したオブジェクトのコピーを実装することが可能となりました。
ES5でのオブジェクトコピー
リスト24:ES5での例
1 | let name = {name: 'Alice' } |
ES2015でObject.assign()を使用したオブジェクトコピー
リスト25:Object.assignメソッドを用いたES2015での例
1 | let name = {name: 'Alice' } |
2 | let heroine = Object.assign({}, name, {age: 7}) |
Object.assign() を使用したオブジェクトのマージ
Object.assign()を使用することで、オブジェクトをマージすることも可能です。オブジェクトのマージの際は、Object.assignの第一引数(targetオブジェクト)に設定されているオブジェクトへ他のオブジェクトがマージされるため、ベースとなったオブジェクトの内容も合わせて更新されます。
リスト26:オブジェクトのマージ
1 | let name = {name: 'Alice' } |
3 | let gender = {gender: 'female' } |
4 | let heroine = Object.assign(name, age, gender) |
オブジェクトの階層が深い場合のマージ
上記のとおりObject.assign()にてオブジェクトのコピー、マージが便利に実施できるようになりましたが、オブジェクトの階層が深くなった場合、深い階層のデータがマージできないという問題があります。
リスト27:深い階層のデータのマージに失敗する例
1 | let heroine = Object.assign({name: 'Alice' }, {status: {age: 7}}, {status: {gender: 'female' }}) |
これについては、npmで公開されているdeep-assignなどを活用して対応可能です。
deep-assign - Recursive Object.assign()
リスト28:deep-assignの使用例
1 | const deepAssign = require( 'deep-assign' ); |
2 | let heroine = deepAssign({name: 'Alice' }, {status: {age: 7}}, {status: {gender: 'female' }}) |
Array
Array.from
Array.fromメソッドは、array-likeオブジェクトやiterableオブジェクトから新しいArrayを作成します。
ES5ではdocument.querySelectorAll()が返すNodeListやargumentsはarray-likeオブジェクトのため、下記のように一度Array型に変換してから使用する必要がありました。
リスト29:ES5での例
03 | let args = Array.prototype.slice.call(arguments); |
04 | console.log(args.join( ' ' )) |
06 | joinString( 'Welcome' , 'to' , 'Japan!' ) |
09 | let el = document.querySelectorAll( 'div' ) |
10 | let divs = Array.prototype.slice.call(el) |
Array.fromを使用することで、array-likeオブジェクトをArray型に変換することができます。
リスト30:ES2015での例
03 | let args = Array.from(arguments); |
04 | console.log(args.join( ' ' )) |
06 | joinString( 'Welcome' , 'to' , 'Japan!' ) |
09 | let el = document.querySelectorAll( 'div' ) |
10 | let divs = Array.from(el) |
また、Array.fromはmapFnを持つため、作成した配列に対してmap関数を実行することができます。
リスト31:Array.fromで作成した配列にmap関数を実行
1 | let admissionFee = [1500, 1000, 700] |
2 | Array.from(admissionFee, fee => fee / 2) |
Array.of
可変長の引数から新しいArrayを作成できます。
リスト32:Array.ofを用いたArrayの作成
1 | Array.of( 'Welcome' , 'to' , 'Japan!' ) |
Array.prototype.copyWithin
第1引数で指定するターゲットで始まる位置に、第2、第3引数で指定したstartとendに対応したインデックスをコピーします。コピーされる範囲は、startからendの1つ前までになります。
リスト33:Array.prototype.copyWithinの使用例
1 | let arr = [0, 1, 2, 3, 4, 5] |
Array.prototype.entries
配列内の各要素に対するkeyとvalueの組み合わせを持つ新しいArrayを作成します。
リスト34:
1 | let hello = [ 'My' , 'name' , 'is' , 'Alice.' ].entries() |
keyのみ、もしくはvalueのみを新しいArrayが欲しい場合は、Array.prototype.keysやArray.prototype.valuesを使用して値を取り出すことができます。