ES2015で導入された、より洗練された構文 Part 1
今回は、ECMAScript 2015(ES2015)で新たに導入された構文を紹介します。
Let + Const
let、constは新たに追加された変数宣言で、var宣言では不可能だった再代入の禁止や再宣言の禁止をすることができます。また、スコープがブレース{}の中に閉じるようになったので、今までよりも保守性の高いプログラムが作成しやすくなりました。
let 「再宣言」が不可
let role = 'Developer' role = 'Reporter' // 再宣言はできません。 let role = 'Guest' // Uncaught TypeError: Identifier 'role' has already been declared
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)
let sum = (param1, param2) => { return param1 + param2 } sum(1, 2) // 3
引数が1つの場合、丸括弧は省略できます。
let fn = param => { return param }
引数を取らない場合、丸括弧が必要です。
let fn = () => { return 'param' }
即時関数は下記のように記述します。
let ua = (() => { return navigator.userAgent })()
簡潔文体(concise body)
波括弧を省略した場合、暗黙的に「return文」になります。
let squared = x => x * x squared(3) // 9
下記のようなobjectリテラルを返すブロック文体のメソッドを、簡潔文体で記述したい場合には、「({ foo: 1 })」のように丸括弧で囲ってください。そうしないと、「{}」が文として解析されてしまうためです。
let fn = () => { return { foo: 1 } }
// 丸括弧で囲わない場合 let fn = () => { foo: 1 } fn() // undefined // 丸括弧で囲った場合 let fn = () => ({ foo: 1 }) fn() // {foo: 1}
Note
jQueryを使用してイベント登録をする際にアロー関数を使う場合は、下記のように「$(this)」に代えて、「$(e.currentTarget)」を使用することで、DOMの取得が可能になります。
$(target).on('click', (e) => { $(e.currentTarget).removeClass('hoge') })
アロー関数を用いると、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な書き方
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の書き方
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となります。
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となります。
class Person { this.name = 'Alice' // Uncaught SyntaxError: Unexpected token }
クラスに変数を持たせる場合は、コンストラクタ内で記述する必要があります。
class Person { constructor() { this.name = 'Alice' // OK } }
コンストラクタ内で定義した変数は、Getter/Setterを使用して取得・設定が可能です。
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メソッドといいます。
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)メソッドを定義できます。静的メソッドは、クラスのインスタンスを生成することなく、「クラス名.メソッド名」の形で呼び出すことができます。
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を記述することで、他のクラスを継承することができます。
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を使用します。
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を返す必要があります。
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
オブジェクトのキー名と変数名が同じだった場合、省略して記述することができます。
let user_id = 12345, count = 200 let options = { user_id, count } // options = { // user_id: 12345, // count: 200 // }
Methods
オブジェクトのメソッドが、function句を使わずに定義できるようになりました。
let counter = { count: 0, increment() { this.count++ } }
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/" // } // ]