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

2016年12月22日(木)
太田 智彬
ECMAScript 2015(ES2015)で追加された以前より洗練された構文を、今回と次回に分けて紹介する。

今回は、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は避けましょう」ということが書かれています。

Airbnb JavaScript Style Guide

アロー関数(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/"
//   }
// ]
リクルートテクノロジーズ ITマネジメント統括部 ディベロップメント3部所属
東京都渋谷区出身。大規模サイトの構築やWebアプリケーションの開発を経て、現在はテクニカルディレクターとしてフロントエンドのチームリード/制作フロー改善に従事。著書:『エンジニアのためのGitの教科書』『ブレイクスルーJavaScript』(翔泳社)『現場で役立つCSS3デザインパーツライブラリ』(MdN)ほか。

連載バックナンバー

Web開発技術解説
第6回

ES2015のモジュール管理

2017/5/9
ES2015に追加されたモジュール管理の機能を紹介し、現行のブラウザから使用する方法を解説する。
開発言語技術解説
第5回

ES2015が備えるモダンな非同期処理

2017/3/28
ES2015に追加された非同期処理の新しい記述方法について学ぶ。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています