素のままのJavaScript(ES5)に直接触れる機会もだんだんと減ってきたので、うろ覚えだったES6の機能をまとめてみました。
なお文中のコードはBabel環境で書きながら確認したため、現行ブラウザでの直接の対応状況に関しては未調査です。
概要
ECMAScriptS6 = ES2015 = JavaScriptの新しいバージョン
- ES5(現行のjs)の次のバージョンとして策定された
- さらに新しいES7も作られている
現状は多くのブラウザが(部分的にしか)対応していないので、ES6で書いたコードを動かすためにはES5への変換(トランスパイル)が必要になる。
トランスパイル用のツールとしてはBabelが代表的な実装になる。
具体的な利用方法としては:
-
ES6をサポートしているブラウザを使う
- 現状、サポートは部分的かつ挙動に差異がある
-
babelのCLIを使って直接トランスパイルする
-
webpack, browserify, sprocketsなどがバンドルを作成する過程にBabelを組み込む
-
Bebel/browser.jsを使って表示のたびにトランスパイルする(デバッグ用)
など。
導入方法は以前React-Redux環境構築の際に調査済なので、今回は省略する。
(React-Redux on Rails(w/webpack/yarn)環境を作ったメモ)
構文の追加/変更
モジュール(import/export)
CommonJSなどサードパーティレベルで実現していたファイル分割/依存関係解決の機能が標準機能となった。
あるモジュール(ファイル)からexportしたクラス・変数・定数・関数を、他のモジュールがimportするかたちで使う。
import/exportの指定方法は復数あるので詳細はMDNなどを参照のこと。
// 変数・定数・関数を、名前を指定して直接出力する
export let someVariable = ...
export const SOME_CONST = ...
export const someFunc = () => {
...
}
// デフォルト(ファイル=モジュールにひとつ)として出力する
export default class SomeClass{
...
}
// モジュール内の全てをSomeModuleにバインドする
import * as SomeModule from 'path/to/module'
// デフォルトを名前空間SomeModuleにバインドする
import SomeModule from 'path/to/module'
// モジュールをバインドせずにロードだけする
import 'path/to/module'
// モジュール内のfooとbarだけロードする
import {foo, bar} from 'path/to/module'
変数と定数(let/const)
var
の代替として、let
とconst
を変数の宣言時に使える。
constで宣言すると定数となり、変更できない。
(Babel環境の場合はトランスパイル時に静的なエラーとなる
letの機能はvarと同じ。
constの対比として、変更できることを明示する意味合いで使う。
let foo = 'foo'
const bar = 'bar'
// OK
foo = 'hoge'
// Error
bar = 'piyo'
クラス(class/extends/super/static)
クラス定義を構文レベルでサポートする。
(シンタックスシュガーにすぎないので、内部的には従来のプロトタイプベース構造から変わっていないとのこと)
class Foo extends SomeSuperClass{
// コンストラクタ
constructor(bar, baz){
// 親クラスのメソッドが呼べる
super()
// インスタンス変数はコンストラクタ内で定義する(これまでと同じ)
this._bar = baz
this._baz = baz
}
// インスタンスメソッド
bar(){
return this._bar
}
// クラスメソッド
static foo(){
return 'FOO'
}
}
let foo = new Foo('BAR', 'BAZ')
console.error(foo.bar())
console.error(Foo.foo())
オブジェクト定義の簡略化
オブジェクト定義の構文が強化された。
let magicKey = 'foo'
let obj = {
// プロトタイプを指定する
__proto__: theProtoObj,
// キー名を省略できる(foo: foo, と同義)
foo,
// メソッドを簡単に書ける
someMethod(){
// クラスと同様にsuper()を呼べる
super()
},
// 動的なキーを使う(obj.foo)
[ magicKey ]: 'magic value'
}
アロー関数(=>)
関数定義の省略記法として()=>
が使える。
アロー関数中のthis
は、その__外側のスコープのthisに等しい__(いわゆるself
を定義しなくていい)
// 基本形
let allowFunc = () => {
// ...
}
// 外側のthisを参照できる
var someObj = {
_someParam: '...',
getSomeParam(){
return () => {
this._someParam // ここでのthisはsomeObj
}
},
}
// 単行の場合は省略可能
let simpleFunc = () => otherFunc()
// 引数がひとつの場合は括弧も省略可能
let funcWithArg = arg => otherFunc(arg)
テンプレート文字列(${}
)
文字列中に変数を埋め込んで、その内容を展開できる。
テンプレート文字列は```で囲む。
(ちなみにmarkdownでよく使うこの文字は、グレイヴ・アクセントというらしい)
const foo = 'foo'
const bar = 'bar'
const baz = () => {
return 'baz'
}
// 変数を展開する
const foobar = `${foo}, ${bar}`
// 式の評価もできる
const some = `${baz()}`
2進/8進リテラル(0b/0o)
2進数、8進数をリテラルとして表現できるようになった。
let bin = 0b1001 // 2進
let oct = 0o11 // 8進
let dec = 9 // 10進
let hex = 0x09 // 16進
デフォルト / 可変長引数(=/...)
引数にデフォルト値を設定できるようになった。
また可変長にも対応した。
// デフォルトをとる
function add(x, y=0){
return x + y
}
add(3)
add(4, 5)
// 可変長引数をとる
function sum(...args){
return args.reduce(function(count, val){
return count + val
}, 0)
}
sum(1, 2, 3)
コレクションの繰り返し(for-of)
繰り返しの制御構文としてfor-of文が追加された。
既存のfor-in との違いは、オブジェクト以外のコレクション(Array, Map, Set, Iterator, String)にも対応していること。
また後述のイテレータを回すこともできる。
// 配列を回す(for-in): for-inなので"インデックス"が"文字列"で返る('0', '1', '2')
for(let n in [10, 20, 30])
console.log(n)
// 配列を回す(for-of): 整数型の値10, 20, 30が返る
for(let n of [10, 20, 30])
console.log(n)
// 文字列も回せる
for(let n of 'abc')
console.log(n)
分割代入
英語ではDestructuring(非構造化)とのこと。
配列やオブジェクトの内容を、復数の変数に(分割して)代入する。
// 配列を分割する
const [foo, bar] = ['foo', 'bar']
console.log(foo)
console.log(bar)
// オブジェクトを分割する(大文字がオブジェクトのキー、小文字が代入される変数)
const {FOO: hoge, BAR: piyo} = {'FOO' : 'foo', 'BAR' : 'bar' }
console.log(hoge)
console.log(piyo)
配列展開
...
で配列を展開し、引数や配列の初期化処理内で利用できる。
let list = [3, 4, 5]
// 展開して結合する
list = [1, 2, ...list, 6, 7]
console.log(list)
// 展開して可変長引数に渡す
print(...list)
function print(...args){
console.log(args)
}
型とオブジェクトの追加/変更
基本型の継承(Array, Date, Element)
ES5では継承できなかった(推奨されていなかった) Array, Date, Elementが継承可能になった。
class CustomArray extends Array{
// ...
}
シンボル型(Symbol)
シンボルは、文字列に似た一意なオブジェクト。
- __プリミティブ型__である
- それ自身とのみ等価(==)、等値(===)となる
let secretKey = Symbol('foo')
let anotherKey = Symbol('foo')
// 同じ文字列で初期化しても、シンボルは別オブジェクトとして認識される
console.log(secretKey == anotherKey) // false
console.log(secretKey === anotherKey) // false
// 文字と比較しても同様
console.log(secretKey == 'foo') // false
console.log(secretKey === 'foo') // false
// 自身とだけ合致する
console.log(secretKey == secretKey) // true
console.log(secretKey === secretKey) // true
rubyの:symbol
のようなリテラル表記は特にない様子だった。
コレクション型(Map/Set/WeakMap/WeakSet)
-
Set = 重複のない配列
-
Map = "連想配列"に近いもの
- 既存のObject(
{ }
)より格納の自由度が高い - かつ、操作用のメソッドが色々ある
- 既存のObject(
-
WeakSet / WeakMap = 弱参照のSet/Map
- 参照(オブジェクト)のみ保持できる
- 格納しても、オブジェクトを保持しない(弱参照) = GCされうる
let set = new Set()
set.add('foo')
set.add('foo') // 値の重複はエラーとはならないが、数にはカウントされない
set.add('bar').add('bar') // メソッドチェーンできる
console.log(set.size) // 2
console.log(set.has('foo')) // true
let map = new Map()
let strKey = 'key'
let listKey = [0, 1, 2]
map.set(strKey, 'string value')
map.set(listKey, 'list value') // 配列オブジェクトをキーにすることができる
console.log(map.get(strKey)) // 'string value'
console.log(map.get(listKey)) // 'list value'
プロミスパターン(Promise)
Promiseパターン実装のためのオブジェクト。
非同期処理を先に実行し、その後からでも結果のコールバックが設定できる。
なのでこれを使うと、
- 処理の順序とコードの記述順序が同じになる
- 非同期処理が連続する場合に、コード上の深いネストを避けられる
const promise = new Promise((resolve, reject) => {
// ...何か非同期的な処理を行う
// 成功したら呼ぶ
resolve()
// 失敗したら呼ぶ
reject()
})
promise.then(() => {
console.log('success');
})
promise.catch(err => {
console.error('failed: ' + err)
})
プロキシパターン(Proxy)
Proxyパターン実装のためのオブジェクト。
もとのオブジェクトをラップしてふるまいを追加/変更する。
// Proxyにつつまれるオブジェクト
var data = {};
// Proxyの処理内容
var handler = {
get: function(obj, name){
// ...
return obj[name]
},
set: function(obj, name, value){
// ...
obj[name] = value
return true
},
};
// Proxyオブジェクトを作って操作
var proxy = new Proxy(data, handler)
proxy.foo = 'FOO'
proxy.bar = 'BAR'
console.log(proxy.foo)
console.log(proxy.bar)
ハンドルできるイベントはget
, set
, construct
, deleteProperty
など色々。
イテレータ(Symbol.iterator)
任意のオブジェクトのSymbol.iterator
プロパティにイテレータを定義することで、for-ofによってループを回すことができる。
let list = {}
// イテレータを定義する
list[Symbol.iterator] = function(){
let i = 0
let iter = {
next: () => {
return { done: false, value: i++}
}
}
return iter
}
// forで取得する
let iter = list[Symbol.iterator]()
for(let i=0; i < 10; i++){
let result = iter.next()
if(result.done) break
console.log(result.value)
}
// for-ofで取得
for(let n of list){
if(n >= 10) break
console.log(n)
}
ジェネレータ
function*
でPython方式のジェネレータを定義できる。
なおBabelでジェネレータ構文を使う場合はbabel-polyfill
パッケージが必要だった。
require('babel-polyfill')
let list = {}
// ジェネレータを定義する
const gen = function*(){
for(let i=0;;)
yield i++
}
// for-ofで生成する
for(let n of gen()){
if(n >= 10) break
console.log(n)
}
参考
-
MDN
-
Stack Overflow