JavaScriptの仕様ES5以前とES2015(ES6)以降の違いの抑えるべき項目

これからJavaScriptを学習するならES2015(ES6)に基づいたコーディングをしたほうがいい項目をピックアップしました。

はじめに

JavaScriptはNetscapeによって開発され、当時、業務提携していたSun MicrosystemsのJavaが注目されていたので名前がJavaScriptとなりました。

主にNetscapeが設立したMozillaが仕様を策定し、Ecma InternationalによってECMAScriptとして標準化されています。

そのため、仕様の版(edition)はECMAScript 5th editionなどの番号が付きます。

2015年に公開されたECMAScript 6th edition(ES6)から「ECMAScript 2015」と年号が付くようになって毎年改訂されています。

このECMAScript 2015(ES2015)ではダイナミックに改訂され、最近のJavaScriptのコーディングはこのときに改訂された内容が主流になりつつあります。

なので特にこれからJavaScriptを学習しようとしている方は、ES2015以降に基づいたコーディングをおすすめします。

その中でもよく使われる、変数、関数、文字列の結合、for文、クラス宣言についてES2015以降の手法を説明し、旧手法との違いを見ていきます。

MozillaやEcma International以外にもGoogleやMicrosoftなどもWebブラウザへの実装に絡んで独自拡張や仕様策定を行っています。

現在は各社Webブラウザの特別な機能拡張は特になく、ECMAScriptの標準仕様に準拠していますが、Microsoftは最新のECMAScript仕様に基づいて「TypeScript」を開発しています。

ES2015の「変数」

JavaScriptで変数を宣言するときはこれまで「var」の記述を行っていました。

ES2015では「let」「const」という宣言が追加されています。

これらを説明する前に変数のスコープについて、どういったものか確認しておきましょう。

変数のスコープとは、その変数を扱うことのできる範囲を指します。

スコープは2種類あります。

  • グローバルスコープ:どこからでもアクセスできます。
  • ローカルスコープ:特定の範囲内だけアクセスできます。

例えば、同じ名前の変数であっても、関数の外で宣言されているものと関数内で宣言されているものはスコープが異なるので別の変数となります。

var x = 'global';
function foo() {
  var x = 'local';
  console.log(x); // local
}
foo();
console.log(x); // global

関数外に宣言されている変数はグローバルスコープとなりどこからでもアクセスできます。

関数内に宣言されている変数はローカルスコープとなりその関数内だけでアクセスすることができます。

この場合、同じ変数名xで2箇所で宣言していますが、関数内で代入した値は、関数外では保持されていないことがわかると思います。

つまり同じ変数名でも別物になっているということです。

変数を追加してスコープの違いをもう少し見てみましょう。

var x = 'global';
var y = 'global-y';
function foo() {
  var x = 'local';
  y = 'local-y';
  var z = 'local-z';
  console.log(x); // local
  console.log(y); // local-y
  console.log(z); // local-z
}
foo();
console.log(x); // global
console.log(y); // local-y
console.log(z); // Uncaught ReferenceError: z is not defined

関数内でグローバル変数yにアクセスできることがわかると思います。

関数外でローカル変数zにアクセスするとエラーになることもわかると思います。

または、「var」を付けづに変数を宣言してもグローバルスコープとなります。

x = 'global';

このように「var」で宣言した変数は、関数の内にあるのか外にあるのかでスコープが変わります。

関数の外で宣言するとグローバルスコープとなり、関数の内で宣言するとローカルスコープとなります。

それでは「let」「const」宣言が追加されてどのように変わったのか見てみましょう。

let

「let」で変数を宣言したときは、コーディングのブロックごとに変数のスコープが変わります。

なので{}内で宣言された変数は{}内だけでアクセスできます。

関数の{}内だけでなく、if文やwhile文など制御文の{}内のブロックごとにスコープが変わります。

また「var」宣言では同じスコープ内で同じ変数名を宣言できたのですが、「let」宣言ではエラーとなります。

「var」と「let」の違いを例文で見てみましょう。

「var」宣言の場合

function test(){
  var x = 0;
  console.log(x); //0
  var x = 1;
  console.log(x); //1
  x = 2;
  console.log(x); //2
  if (true){
    console.log(x) //2
    var x = 3;
    var y = 'a';
  }
  console.log(x); //3
  console.log(y); //a
}

「var」で宣言した場合は、同じ関数内で宣言されれば、制御文(if, whileなど)内で宣言されてもその外からアクセスできています。

「let」宣言の場合

function test2(){
  let x = 0;
  console.log(x); //0
  let x = 1;
  console.log(x); //error
  x = 2;
  console.log(x); //2
  if (true){
    let x = 3;
    let y = 'a';
    console.log(x) //3
  }
  console.log(x); //2
  console.log(y); //error
}

同じコードの「var」を「let」で宣言した場合は、同じ関数内で同名の変数宣言はエラーとなり、制御文(if, whileなど)内での宣言は同名でもその外のものとは違う変数として扱われていることがわかります。

ES5以前は変数のスコープが関数の内か外でしか変えられなかったため、コード量が増えると重複したり意図しない値の変更などの混乱が懸念されていました。

そこで無名関数を使って回避するテクニックが使われていました。

例えば、以下の制御文ではcnt変数を使ってカウントしていますが、制御文の外でも同名の変数を使っている可能性があり値を変えたくない場合、変数名を変えても重複するリスクは避けられません。

for (var cnt=1; cnt<3; cnt++){
console.log(cnt); // 1, 2
}
console.log(cnt); // 3

この場合、即時実行関数を作成して回避できます。

無名関数内に上記の制御文をコーディングすることで関数内に変数を宣言可能にし、即時実行するように記載します。

var count = (function(){
for (var cnt=1; cnt<3; cnt++){
console.log(cnt); // 1, 2
}
})();
console.log(cnt);// error

このようにして変数のスコープをコントロールするテクニックを使っていました。

このテクニックは関数内で関数を作ったり、オブジェクトに関数を定義するなどいろいろ活用できますが、記述が少し複雑なため多用することでコードが読みづらくなってしまいます。

ES2015以降は「let」宣言によって同じようにスコープをコントロールできるので、即時実行関数を使わずに「let」で変数を宣言するようにしましょう。

const

「const」で変数を宣言した場合は、一度値を代入すると変更できません。

const x = 123;
x = 456; // error

例えば、値を変更したくない定数や関数を作るときなどで宣言します。

const VAT = 1.08;
let goods, price;
if (true) {
  goods = 100;
  price = goods * VAT; 
}
console.log(price);

ES2015の「関数」

JavaScriptで関数を定義するときはこれまで基本的にfunction文やfunction式を使っていました。

function foo(param) {
  console.log(param);
}

ES2015で以下の記述方式が追加されました。

const foo = (param) => {
  console.log(param);
};
foo(1);

「=>」を「アロー」と呼びます。

この場合の関数の定義は、「()=>{}」と記述することで定義されます。

このままだと無名なので、変数に関数を代入することで変数名が関数名になります。

const foo = () => {};
foo();

変数に関数を代入するときは文末に「;」を記入することを忘れないようにしましょう。

もちろん引数を指定することもできます。

「(引数)=>{関数内処理}」となります。

アローを使って定義していることから「アロー関数」ともいわれます。

無名関数なので、引数に関数を指定するような処理に直接記述することもできます。

button.addEventListener('click', ()=>{
  クリック時の処理
});

これをES5以前の書き方にすると以下のようになります。

button.addEventListener('click', function(){
  クリック時の処理
});

アロー関数とfunctionによる関数定義では、「this」の中身が異なるの場合があるで注意してください。

例えばオブジェクトに関数を定義したときに、アロー関数とfunctionによる定義では「this」の中身が変わります。

functionによる定義の場合「this」はそのオブジェクトになります。

let obj = {
  scope: this,
  hoge: function(){
    console.log(this); // obj
  }
};
obj.hoge();
console.log(obj.scope); // Window

アロー関数の場合「this」はWindowオブジェクトになります。

let obj = {
  scope: this,
  hoge: ()=>{
    console.log(this); // Window
  }
};
obj.hoge();
console.log(obj.scope); // Window

これまで使う場所によって「this」の中身が違って注意してコーディングしていたのが、アロー関数を使うことによって「this」の扱いも整理されるように思います。

ES2015の「文字列の結合」

ES5以前は変数に入っている文字列も含めて文字列を結合するときは「+」記号を使っていました。

let price = 100;
console.log('この商品の金額は税抜で' + price + '円となります。');

ES2015以降はテンプレート文字列といって、バッククォート「`」で文字列と変数を囲ってまとめることができます。

let price = 100;
console.log(`この商品の金額は税抜で${price}円となります。`);

またテンプレート文字列では改行などの制御コードもそのまま扱えるためエディタで記述した見た目通りに表示されます。

ES5以前で改行する場合は「\n」を記述

let price = 100;
console.log('この商品の金額は\n'
          + '税抜で' + price + '円となります。');

ES2015以降は見た目通りに表示

let price = 100;
console.log(`この商品の金額は
税抜で${price}円となります。`);

スペースやタブも反映されるのでインデントを入れないように気をつけましょう。

ES2015の「for文」

ES2015から「for 〜 of文」が追加されました。

これは配列から1要素ずつ順番に取り出して繰り返しできる文です。

let wdays = ['日', '月', '火', '水', '木', '金', '土'];
for (let day of wdays) {
  console.log(`${day}曜日`);
}

ES5以前の書き方は以下です。

var wdays = ['日', '月', '火', '水', '木', '金', '土'];
for (var i=0; i<7; i++) {
  console.log(wdays[i] + '曜日');
}

ES2015の「クラス宣言」

オブジェクト指向的なコーディングをするのに、ES5以前は関数を使って表現していました。

function Profile() {
  this.name = '太郎'; // プロパティ
}
Profile.prototype.desc = function() { // メソッド
  console.log(this.name + 'はお人好しでカワイイです。');
};
var person = new Profile();
person.desc();

ES2015以降は他の言語と同じようにクラス宣言ができるようになりました。

class Profile {
  constructor() {
    this.name = '太郎';
  }

  desc() {
    console.log(`${this.name}はお人好しでカワイイです。`);
  }
}
let person = new Profile();
console.log(person.name); // 太郎
person.desc(); // 太郎はお人好しでカワイイです。

特に他の言語でクラスを使っている人は、JavaScriptで同じようなコーディングをするのにややこしくなっていたかもしれません。

ES2015からクラス宣言ができるようになって、直感的に同等のコーディングができるようになったと思います。

また、このクラスでは静的メソッドを呼び出すこともできます。

「static」を宣言してメソッドを作成します。

class Profile {
  constructor() {
    this.name = '太郎';
  }

  desc() {
    console.log(`${this.name}はお人好しでカワイイです。`);
  }

  static showProfile() {
    console.log(this.name + 'はお人好しでカワイイです。' + this.getHeight());
  }
  static getHeight() {
    return '身長は160cmです。';
  }
}

Profile.showProfile();

「static」で宣言されたメソッドはインスタンスを生成せずに実行できます。

反対にインスタンスからはメソッドを呼び出すことはできません。

最後に

様々な環境で使われることの多いJavaScriptですが、次第に記述が複雑になり悩まされていた人もいたかと思います。

ES2015で策定された仕様から少しはそういった状況から緩和される可能性もあるので、特にこれからJavaScriptを学習しはじめる人は積極的に新しい仕様でコーディングしていきましょう。