ES2015で導入された、より洗練された構文 Part 1

今回は、ECMAScript 2015(ES2015)で新たに導入された構文を紹介します。
Let + Const
let、constは新たに追加された変数宣言で、var宣言では不可能だった再代入の禁止や再宣言の禁止をすることができます。また、スコープがブレース{}の中に閉じるようになったので、今までよりも保守性の高いプログラムが作成しやすくなりました。
let 「再宣言」が不可
リスト1:letの使用例
let role = 'Developer' role = 'Reporter' // 再宣言はできません。 let role = 'Guest' // Uncaught TypeError: Identifier 'role' has already been declared
const 「再宣言」も「再代入」も不可
リスト2:constの仕様例
// constを宣言する時は、初期値を入れなければいけません。 const role // Uncaught SyntaxError: Missing initializer in const declaration const role = 'Developer' // 再宣言はできません。 const role = 'Reporter' // Uncaught TypeError: Identifier 'role' has already been declared // 再代入もできません。 role = 'Guest' // Uncaught TypeError: Assignment to constant variable.
letとconstの使い分けですが、まずconstを使用し再代入が必要な場合のみletを使うようにしましょう。こうすることで、別の開発者がソースを読むときに変数の後の動きが予測しやすくなるのでリーダブルなコードになります。GitHubのStar 44,000以上と絶大な人気を誇るAirbnbのJavaScriptコーディング規約においても、「まずconstを使い、varは避けましょう」ということが書かれています。
アロー関数(Arrows)
アロー関数式は、従来のfunction式を短く簡潔に書くことができるものです。ただし、この式は単なる省略記法というだけでなく、thisの値を束縛することに注意が必要です。function式ではthisは自身を参照していましたが、アロー関数式でオブジェクトのメソッドとして呼び出された場合は、コンテキストのオブジェクトがthisとなります。
ブロック文体(block body)
リスト3:アロー関数の例
let sum = (param1, param2) => {
return param1 + param2
}
sum(1, 2) // 3
引数が1つの場合、丸括弧は省略できます。
リスト4:引数が1つの時は丸括弧を省略可
let fn = param => {
return param
}
引数を取らない場合、丸括弧が必要です。
リスト5:引数がない時は丸括弧が必要
let fn = () => {
return 'param'
}
即時関数は下記のように記述します。
リスト6:即時関数の記述例
let ua = (() => {
return navigator.userAgent
})()
簡潔文体(concise body)
波括弧を省略した場合、暗黙的に「return文」になります。
リスト7:簡潔文体
let squared = x => x * x squared(3) // 9
下記のようなobjectリテラルを返すブロック文体のメソッドを、簡潔文体で記述したい場合には、「({ foo: 1 })」のように丸括弧で囲ってください。そうしないと、「{}」が文として解析されてしまうためです。
リスト8:元のメソッド
let fn = () => {
return {
foo: 1
}
}
リスト9:簡潔文体での記述例
// 丸括弧で囲わない場合
let fn = () => { foo: 1 }
fn() // undefined
// 丸括弧で囲った場合
let fn = () => ({ foo: 1 })
fn() // {foo: 1}
Note
jQueryを使用してイベント登録をする際にアロー関数を使う場合は、下記のように「$(this)」に代えて、「$(e.currentTarget)」を使用することで、DOMの取得が可能になります。
リスト10:jQueryのイベント登録にアロー関数を使う
$(target).on('click', (e) => {
$(e.currentTarget).removeClass('hoge')
})
アロー関数を用いると、Arrayメソッドを簡潔に記述することができます。
リスト11:アロー関数を使ったArrayメソッドの書き方
let users = [
{
name: 'Alice',
age: 19,
gender: 'female'
},
{
name: 'Bob',
age: 24,
gender: 'male'
},
{
name: 'Carol',
age: 27,
gender: 'female'
},
{
name: 'Charlie',
age: 21,
gender: 'male'
}
]
users.map(user => user.age)
// [19, 24, 27, 21]
users.filter(user => user.gender === 'female')
// [
// {
// name: 'Alice',
// age: 19,
// gender: 'female'
// },
// {
// name: 'Carol',
// age: 27,
// gender: 'female'
// }
// ]
users.every(user => user.age >= 18)
// true
users.some(user => user.gender === 'male')
// true
Classes
これまでのJavaScriptではclassを作る仕組みがなく、prototypeでclass likeな実装をするしかありませんでした。しかしES2015でようやく、正式にclassが標準のインターフェースとして実装されました。
class構文を使用することで、複雑な記述になりがちなprototype構文を使用することなく、他のプログラミング言語のように簡潔にクラスを記載することができるようになります。
ES5までのclassの書き方とES2015でのclassの書き方の違いは、下記のとおりです。
ES5でのclass likeな書き方
リスト12:ES5でのclass likeな書き方
var Person = function(name, age) {
this.name = name
this.age = age
}
Person.prototype = {
getName: function() {
return this.name
},
sayHello: function() {
console.log("Hello I'm " + this.getName())
}
}
ES2015でのclassの書き方
リスト13:ES2015でのclassの書き方
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
getName() {
return this.name
}
sayHello() {
console.log("Hello I'm " + this.getName())
}
}
メソッド定義
Constructor method
constructorメソッドは、クラスによって定義されるオブジェクトが生成されるときに実行されるメソッドです。主にクラス内で共通して使われるプロパティの初期値を定義する際などに使用されます。constructorというメソッドは1つのクラスに1つしか定義できず、2つ以上定義されている場合はSyntax Errorとなります。
リスト14:constructorメソッド
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
}
let alice = new Person('Alice', 7)
alice.name // alice
alice.age // 7
ES2015のクラスの仕様では、クラス直下の本体部分で定義できるのはメソッドのみです。クラス変数としてデータを保持する変数や定数は定義できないため、注意が必要です。クラス直下でインスタンスを定義しようとした場合は、Syntax Errorとなります。
リスト15:クラス直下に変数は定義できない
class Person {
this.name = 'Alice' // Uncaught SyntaxError: Unexpected token
}
クラスに変数を持たせる場合は、コンストラクタ内で記述する必要があります。
リスト16:変数はコンストラクタ内に記述する
class Person {
constructor() {
this.name = 'Alice' // OK
}
}
コンストラクタ内で定義した変数は、Getter/Setterを使用して取得・設定が可能です。
リスト17:コンストラクタ内で定義した変数のハンドリング
class Calculator {
constructor(arr) {
this.length = arr.length
this.width = arr.width
}
// getter
get area() {
return this.length * this.width
}
// setter
set parameter(arr) {
this.length = arr.length
this.width = arr.width
}
}
const calculator = new Calculator({length: 10, width: 5})
console.log(calculator.area) // 50
calculator.parameter = {length: 20, width: 10}
console.log(calculator.area) // 200
Prototype methods
クラスのインスタンスから呼び出せるメソッドを、prototypeメソッドといいます。
リスト18:prototypeメソッド
class Square {
constructor(height, width) {
this.height = height
this.width = width
}
area() {
return this.height * this.width
}
}
const square = new Square(10, 10)
square.area() // 100
Static methods
staticキーワードを使用することで、クラスに静的(static)メソッドを定義できます。静的メソッドは、クラスのインスタンスを生成することなく、「クラス名.メソッド名」の形で呼び出すことができます。
リスト19:静的メソッド
class ObjectPolyfill {
static values(obj) {
return Object.keys(obj).map(key => obj[key])
}
static entries(obj) {
return Object.keys(obj).map(key => [key, obj[key]])
}
}
ObjectPolyfill.values({
name: 'Alice',
age: 19,
gender: 'female'
})
// ["Alice", 19, "female"]
ObjectPolyfill.entries({
name: 'Alice',
age: 19,
gender: 'female'
})
// [["name", "Alice"], ["age", 19], ["gender", "female"]]
Sub classing with extends
クラスを継承する際は、extendsを使用します。クラス宣言内にextendsを記述することで、他のクラスを継承することができます。
リスト20:extendsを用いた継承
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
getName() {
return this.name
}
sayHello() {
console.log("Hello I'm " + this.getName())
}
}
class Employee extends Person {
constructor(name, age, salary) {
super(name, age)
this.salary = salary
}
getSalary() {
return this.salary
}
}
let employee = new Employee('Alice', 19, 10000)
employee.getName() // Alice
employee.sayHello() // Hello I'm Alice
employee.getSalary() // 10000
Super class calls with super
親クラスのメソッドを呼び出すには、superを使用します。
リスト21:superを用いて親クラスのメソッドを呼び出す
class Cat {
constructor(name) {
this.name = name
}
speak() {
console.log(this.name + ' makes a noise.')
}
}
class Lion extends Cat {
speak() {
super.speak()
console.log(this.name + ' roars.')
}
}
let lion = new Lion('Mike')
lion.speak()
// Mike makes a noise.
// Mike roars.
デコレーターパターン
ES2015の次期バージョンでは、動的にオブジェクトの機能を拡張できるデコレーターパターンの導入が検討されています。現在はStage2にて検討が進められています。デコレーターパターンが導入されれば、クラスやメソッドに対してアノテーションを使用することで動的にクラスの機能を追加していくことができるようになり、さらにclass構文を活用する場が広がります。
なおアノテーションの関数に引数を渡すには、functionを返す必要があります。
リスト22:デコレーターパターン導入後のコード例
class Person {
constructor() {
this.firstname = "Alice"
this.lastname = "Liddell"
}
@readonly(true)
fullName() {
return this.firstname + " " + this.lastname
}
}
function readonly(value) {
return function (target, key, descriptor) {
descriptor.writable = !value
return descriptor
}
}
現時点での検討状況、提案内容については下記のサイトを参照してください。
ECMAScript Active Proposals
Decorators summary
Enhanced Object Literals
ES2015では、オブジェクトリテラルに便利な構文が追加されました。
Shorthand
オブジェクトのキー名と変数名が同じだった場合、省略して記述することができます。
リスト23:Shorthandの例
let user_id = 12345,
count = 200
let options = { user_id, count }
// options = {
// user_id: 12345,
// count: 200
// }
Methods
オブジェクトのメソッドが、function句を使わずに定義できるようになりました。
リスト24:function句なしでメソッドを定義
let counter = {
count: 0,
increment() {
this.count++
}
}
Computed property
オブジェクトのキー名を計算して評価することが可能になりました。
リスト25:Computed propertyの例
let searchResultItems = [
{
title: "株式会社リクルートテクノロジーズ",
url: "http://recruit-tech.co.jp/"
},
{
title: "株式会社リクルートホールディングス",
url: "http://www.recruit.jp/"
}
]
let insertItems = searchResultItems.map((item, index) => {
return {
["title_" + "index"]: item.title,
["url_" + "index"]: item.url
}
})
// [
// {
// "title_0": "株式会社リクルートテクノロジーズ",
// "url_0": "http://recruit-tech.co.jp/"
// },
// {
// "title_1": "株式会社リクルートホールディングス",
// "url_1": "http://www.recruit.jp/"
// }
// ]


