JavaScript
はECMAScript
という仕様のもとに実装されています。ECMAScript2015(ES2015)
はECMAScript
の新しい標準仕様です。この仕様は2015年6月に確定しました。
ES5
から6年ぶりのメジャーアップデートのため最初は違和感があるかもしれません。
しかし、より便利になるアップデートですのでしっかりキャッチアップし積極的に使用していきましょう。
ネット上でES6
とES2015
を見かけると思いますが、両方とも同じものを指しています。正確にはES2015
が正しいです。来年以降もES2016
,ES2017
…とアップデート予定です。当初はES6
として仕様検討が始まったためその名残りです。
背景
ES2015
の仕様確定が2015年6月にされましたが、すぐに新仕様が使えるわけではありません。JavaScriptエンジン(V8,JavaScriptCore,Chakra等)の実装は現在進行中でまだ使えない機能があるためです。こちらのサイトで各ブラウザに実装済みの機能が確認できます。
ブラウザとJavaScriptエンジンの対応は以下の表のとおりです。
ブラウザ | JavaScriptエンジン |
---|---|
IE | Chakra |
Chrome | V8 |
Opera | V8 |
Safari | JavaScriptCore |
Firefox | SpiderMonkey |
Babel
ブラウザに新仕様が追加されるまで新仕様を導入することがきません。そこで登場したのがBabel(6to5)
で、Babel
はES2015,ES Next
のコードをES5
のコードにトランスパイルしてくれるツールです(公式サイト)。ES2016
,ES2017
の様な次期仕様をES Next
と呼んでいます。ブラウザはトランスパイル後のJavaScriptコードを読み込むことになります。
ここでは特によく使われている仕様を説明します。
let
, const
はブロックスコープの変数宣言かできます。const
は再代入不可な値を宣言します。これまで使われていたvar
は使用しないようにしましょう。基本はconst
、再代入が必要な場合のみlet
を使用するようにしましょう。
// ES5
var a = 1;
// ES2015
let b = 1; // 再代入不可能な値(再代入が必要なときのみ使用する)
const c = 1; // 再代入不可な値(推奨)
let
, const
はvar
とはスコープのルールが違います。
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
これはvar
がグローバルオブジェクトのproperty
となるためです。(ブラウザの場合はwindow object
)
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
また、var
との違いとして、Temporal Dead Zone(TDZ)
があります。変数宣言は、ブロックの先頭か、変数を使用する直前で宣言するようにしましょう。
{
console.log(a); // undefined
console.log(b); // Reference Error!
var a;
let b;
}
const
は変更不可と説明しましたが、配列やオブジェクトの要素は変更が可能です。
{
const a = [1,2,3];
a.push(4);
console.log(a); // 1,2,3,4
a = 42; // Type Error!
}
配列やオブジェクトの要素も変更不可にするにはObject.freeze()やimmutable.js等を使うことになります。
バッククォートを使った文字列の宣言ができます。メリットは文字列結合が簡単に書けることです。バッククォートで囲った文字列の${}
は展開されます。またバッククォート内は改行できます。
// ES5
var errorCode = 404;
var errorMessage = `file not found.`;
console.error('Error!! Code: ' + errorCode + ', Message: ' + errorMessage);
// Error!! Code: 404, Message: file not found.
// ES2015
const errorCode = 404;
const errorMessage = `file not found.`;
console.error(`Error!! Code: ${errorCode}, Message: ${errorMessage}`);
// Error!! Code: 404, Message: file not found.
クラスの宣言ができます。extends
による継承や、instance object
が生成直後に実行されるconstructor
が使用できます。
class User {
constructor(name) {
this.name = name;
}
say() {
return `My name is ${this.name}`;
}
}
// Userクラスを継承してAdminクラスを作る
class Admin extends User {
say() {
return `[Administrator] ${super.say()}`;
}
}
const user = new User('Alice');
console.log(user.say()); // My name is Alice
const admin = new Admin('Bob');
console.log(admin.say()); // [Administrator] My name is Bob
関数の引数にデフォルト値を設定できます。
// ES5
function consoleName(name) {
name = (name != undefined) ? name : 'Taro';
console.log('username: ' + name);
}
// ES2015
function consoleName(name = 'Taro') {
console.log(`username: ${name}`);
}
consoleName(); // Taro
consoleName('Masaya'); // Masaya
functionを=>
に置換できます。
// ES5
$('.hoge').on('change', function(event) {
console.log(event);
});
// ES2015
$('.hoge').on('change', (event) => {
console.log(event);
});
// 中身が式なら`{}`を省略可能
$('.hoge').on('change', (event) => console.log(event));
// 式で書いた場合は、結果がreturnされます
const multify = (val) => val * val;
console.log(multify(5)); // 25
※注意点
this
の扱いがfunction
と=>
で異なります。
class StatusCode {
constructor(statusCode) {
this.statusCode = statusCode;
setTimeout(() => {
console.log(this.statusCode, 'OK');
}, 1000);
setTimeout(() => console.log(this.statusCode, 'OK'), 1000);
const self = this;
setTimeout(function() {
if (this.statusCode === undefined) console.log('`this.statusCode` is `undefined` in function');
console.log(self.statusCode, 'OK');
}, 1000);
}
}
const statusCode = new StatusCode(200);
Allow Function
内のthis
はStatusCode
クラスのthis
を参照するのに対し、function()
はグローバオブジェクトのthis
を参照している。グローバルオブジェクトのthis
にstatusCode
は宣言していないのでundefined
となります。
オブジェクトリテラルが拡張されました。
// ES5
var obj = { "name": "Masaya Kamakura" };
// ES2015
// key名とvalueの変数名が同じであれば省略可能
function getNameObject(name) {
return { name };
}
console.log(getNameObject('Masaya Kamakura')); // {"name":"Masaya Kamakura"}
// key名を動的に宣言可能
function getNameObject(name) {
const nameKey = 'fullName';
return { [nameKey]: name }; // keyに変数を使用
}
console.log(getNameObject('Masaya Kamakura')); // {"fullName":"Masaya Kamakura"}
function getNameObject(name) {
return { [(() => 'fullName')()]: name }; // keyに関数を使用
}
console.log(getNameObject('Masaya Kamakura')); // {"fullName":"Masaya Kamakura"}
APIがいくつか追加されました。ここではincludes
とrepeat
、startsWith
を紹介します。その他の機能はこちらを参考にしてください。
"abcde".includes("cd") // true
"abcde".includes("dd") // false
"abc".repeat(3) // "abcabcabc"
"abcde".startsWith("abc") // true
"abcde".startsWith("de") // false
-
includes()
- 引数に指定した文字列が含まれていれば
true
、以外はfalse
を返す
- 引数に指定した文字列が含まれていれば
-
repeat()
- 引数に指定した回数だけ繰り返した文字列を返す
-
startsWith()
- 引数に指定した文字列で開始していれば
true
、以外はfalse
を返す
- 引数に指定した文字列で開始していれば
配列を展開して返します。 配列に要素を追加して、新しい配列を作るときによく利用されます。
const list = [1, 2, 3];
console.log(...list); // 1 2 3
// 先頭に要素を追加する
const newList = [0, ...list];
console.log(newList); // [0, 1, 2, 3]
// Default Parametersと組み合わせて使う
function foo(x, y, ...z) {
console.log(x, y, z);
}
foo(1, 2, 3, 4, 5); // 1 2 [3,4,5]
import
: 外部モジュールを読み込むexport
: 外部モジュールで利用できるようにする
// a.js
import * as fuga from "b"; // exportされたすべてのモジュールを読み込む
import { hoge1, hoge2 } from "b"; // hoge1, hoge2を読み込む
import foo from "b"; // `{}`をつけないとexport defaultされたモジュールを読み込む
// b.js
export default function foo() {};
export function hoge1() {};
export function hoge2() {};
1つ以上のsource
オブジェクトの保有する全てのプロパティをtarget
にコピーします。戻り値はtarget
オブジェクトになります。
const todo = {
id: 1,
text: 'catch up es6',
status: 'pending'
};
const newTodo = Object.assign({}, todo, {status: 'progress'});
console.log(newTodo);
// { "id": 1, "text": "catch up es6", "status": "progress" }
callback
のような非同期プログラミングで使用する。
callback
を使った例
getAsync("fileA.txt", (error, result) => {
if(error){// 取得失敗時の処理
throw error;
}
// 取得成功の処理
});
Promise
を使った例
var promise = getAsyncPromise("fileA.txt"); // textを取得してPromise Objectを返す
promise.then((result) => {
// 取得成功の処理
}).catch((error) => {
// 取得失敗時の処理
});
getAsyncPromise()
はPromise Object
を返すfunction
です。処理が成功した場合then()
。失敗した場合catch()
が実行されます。
次に、getAsyncPromise()
の中身を見てみましょう。
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (false) { // この例では実行させない
reject(new Error('ERROR!'));
}
resolve('Async Hello world');
}, 16);
});
}
Promise
にはPromise.all()
,Promise.race()
という機能が用意されています。どちらも並列に非同期処理を実行させることができます。違いはthen
,catch
が実行されるタイミングです。
// Promise.all()のthen()はすべての処理が完了(resolve/reject)したら実行される
Promise.all([getAsyncPromise(url1), getAsyncPromise(url2), getAsyncPromise(url3)])
.then(results => console.log(results))
.catch(error => console.log(error));
// Promise.race()のthen()はどれか一つでも完了(resolve/reject)したら実行される(他の処理が中断されることではない)
Promise.race([getAsyncPromise(url1), getAsyncPromise(url2), getAsyncPromise(url3)])
.then(results => console.log(results))
.cache(error => console.log(error));
このような機能のおかげでcallback地獄
になりにくいコード書くことができます。上記のPromise.all()
の例をcallback
で書くこと以下のようになります。(error処理は省略)
asyncFunction().then(function (value) {
console.log(value); // 'Async Hello world'
}).catch(function (error) {
console.log(error); // reject()が実行された場合はこの行が処理される
});
※補足
Promise
以外にもgenerator(ES2015)
やaync function(ES Next)
のような非同期プログラミングをするための機能があります。
与えられた関数を、配列の各要素に対して一度ずつ実行します。
const data = [1, 2, 3, 4, 5];
data.forEach((val) => console.log(val));
for文だとループ変数が必要であったり、階層が深くなると可読性が下がるので使用しないようにしましょう。
与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。
Array.prototype.forEach()
と似ていますが、新しい配列を生成するところに違いがあります。
const data = [1, 2, 3, 4, 5];
const square = data.map((val) => val * val);
console.log(square); // [1, 4, 9, 16, 25]
引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。
const data = [1, 2, 3, 4, 5];
// 要素が4未満の場合のみ2乗した結果を返す
const filterSquare = data.filter((val) => val < 4)
.map((val) => val * val);
console.log(filterSquare); // [1, 4, 9]
隣り合う 2 つの配列要素に対して(左から右へ)同時に関数を適用し、単一の値にします。
const data = [1, 2, 3, 4, 5];
const sum = data.reduce((pre, current) => pre + current);
console.log(sum); // 15
const max = data.reduce((pre, current) => Math.max(pre, current));
console.log(max); // 5
sumの例
pre | current | return | |
---|---|---|---|
1st | 1 | 2 | 3 |
2nd | 3 | 3 | 6 |
3rd | 6 | 4 | 10 |
4th | 10 | 5 | 15 |
結果: 15
maxの例
pre | current | return | |
---|---|---|---|
1st | 1 | 2 | 2 |
2nd | 2 | 3 | 3 |
3rd | 3 | 4 | 4 |
4th | 4 | 5 | 5 |
結果: 5
この資料で紹介した機能はES2015の仕様でもごく一部です。さらに勉強したい方のために参考資料を残しておきます。
またES5で便利な関数も紹介しました。for
,if
,switch
等を使わなくても書けることが多いので、ES5の機能で書けないか考えるようにしましょう。