webプロダクトいんふぉ

webの気になった情報を発信中!!

ES6で提供された主な仕様

ECMAScript6とは?

そもそもECMAScriptとは、標準化団体によって標準化された言語仕様で、JavaScriptが動作する際に、元となっている仕様です。

ES6で提供された新たな仕様(1部抜粋)

・class命令によるJava/C#ライクなクラス定義が可能に

・import/export命令によるコードのモジュール化に対応

・関数構文の改善

・let/const命令によるブロックスコープの導入

・for...of命令による値の列挙

イテレーター/ジェネレーターによる列挙可能なオブジェクトの操作が可能に

・promise、コレクション(Map/Set)/Proxyなどの組み込みオブジェクトを追加

・string/Number/Array/Objectなどの既存組み込みオブジェクトの拡充など

ブロックスコープを有効にする ー let命令

これまでのJavaScriptには、ブロックレベルのスコープはありませんでした。

※ES6を試してみたい方は、https://babeljs.io/repl/を利用ください

if (true) {
  var i = 1;
}
conosole.log(i); // 結果:1

ブロックスコープが有効であれば、結果は未定義エラーとなるはずですが、JavaScriptではブロックスコープの概念がないので、変数iはブロック外でも有効となり、結果は1です。そこで、ES6のlet命令を利用することでブロックスコープで有効な変数を宣言できます。

if (true) {
  let i = 1;
}
conosole.log(i); // 結果:エラー(i is not defined)

今度は、結果はエラーになります。let命令で宣言された変数はブロック外では無効になったためです。

let命令はスコープ内での変数の重複を認めていない(var命令は可)

ES6でもvar命令は宣言できますが、「スコープは最大限限定すべき」「重複チェックコンパイラーに任せられる」などの理由から、基本はlet命令を優先して利用することになると思います。

「let命令はスコープ内での変数の重複を認めていない」と書きましたが、どういうことかというと

switch(i) {
  case 0:
  let value = 'i:0';
  break;
  case 1:
  let value = 'i:1'; // 重複エラー
  break;
}

case1の時にも同じ変数名を定義しているので、エラーになってしまいます。

即時関数は利用しない

即時関数は、関数ブロックで疑似的にスコープを形成し、グローバルスコープの汚染を防ぐテクニックです。

(function(){
  // 関数の中身
})();

ES5までは当然の仕組みですが、ES6ではコード全体をブロックで括り、配下の変数をlet命令で宣言すれば、即時関数と同じ効果が得られるので、利用する必要がなくなります。

定数を宣言する ー const命令

const命令は、定数を宣言できます。PHPのfinalと同じです。

const data = 100;
data = 150; // エラー

ただし、const命令の定数は正しくは「再代入できない」であって、「変更できない」ではない点に注意です。

const data = [1, 2, 3];
data[0] = 10;
console.log(data); // 結果:[10,2,3]

配列は参照型なので、dataの参照先に再代入しているわけではなく、中身の要素だけを書き換えているため、constの制約にはかかりません。

const data = [1, 2, 3];
data = [10, 2, 3]; // エラー

配列そのもを変更した場合にはエラーになります。

文字列リテラルへの変数/改行の埋め込みを可能にする ー テンプレート文字列

テンプレート文字列を利用することでエスケープシーケンスで表現していた文字を、文字リテラルの中でそのまま表現できます。テンプレート文字列はクォート「'」「"」の代わりに、「`」(バッククォート)で括ります。

let str = `Hello
World`;
console.log(str); //Hello「改行」World

これまでであればエスケープシーケンスで表現していた改行文字を、文字列リテラルの中でそのまま表現できます。

また、${}の形式で、変数を文字列に埋め込むことも可能です。今までは、変数と文字は「+」演算子を使って連結する必要がありました。

let name = 'Tom';
console.log(`Hello,${name}!`); // Hello,Tom!

新たなデータ型Symbol

従来のNumber、String、Objectなどの型に加えて、新たにSymbolという型が追加されました。これは下手な説明より見た方がはやいです。

let hoge = Symbol('hoge');
let hoge2 = Symbol('hoge');
console.log(typeof hoge); // 結果:symbol
console.log(hoge.toString()); // 結果:symbol(hoge)
console.log(hoge === hoge2); // 結果:false

ここで注意するのは一点だけです。Symbolで生成したものは中身が同じであろうと、別物とみなされます。

また、シンボルでは文字列や数値への暗黙的な変換はできません。つまり、Symbolの型と文字列・数値の連結はできません。

ただし、boolean型、object型への変換は可能です。

これって実際どんな場面で活用するの?となると思います。

具体的な利用例をいくつか下記に挙げます。

①定数の値として利用する
var JAVASCRIPT = 0;
var RUBY = 1;
var PERL = 2;
var PYTHON = 3;

一般的に、このような定数は、JAVASCRIPTRUBYPERLなどを識別するための定数であって、割り当てられた0、1、2...といった値には意味がありません。ただ、これらの定数を利用するコードでは、定数、数値を利用してもエラーにはなりません。

コードの可読性を考えれば「0」で比較するのは望ましい状態ではありませんし、そもそも「var HOGE = 0;」のような定数が現れた時に、同じ値の定数が混在してしまうのはバグが混入する元です(枠割が似ていれば尚更)

そこで定数の値としてSymbolを利用します。

const JAVASCRIPT = Symbol();
const RUBY = Symbol();
const PERL = Symbol();
const PYTHON = Symbol();

Symbol命令で生成されたシンボルは、同名であってもユニークになります。つまり、定数JAVASCRIPTと等しいのは定数JAVASCRIPTのみです。

②非公開なプロパティを定義する

たとえば以下は、MyClassクラスの中で、privateなSECRETプロパティを定義する例です。

// SECRETプロパティの名前でシンボルで準備
const SECRET = Symbol();
class Myclass {
  constructor(secret) {
    this.data1 = 1;
    this.data2 = 2;
    // SECRETプロパティに値を設定
    this[SECRET] = secret;
  }
  // SECRETプロパティを利用したメソッド
  checkSecret(secret) {
    return this[SECRET] === secret;
  }
}
let c = new Myclass(12345);
// メソッド経由ではSECRETプロパティにアクセスできる
console.log(c.checkSecret(12345)); // true
// SECRETプロパティへの直接アクセスは不可
console.log(c.secret); // undifined
// オブジェクトのキー(プロパティ)を列挙
console.log(Object.keys(c)); // ["data1","data2"];
// オブジェクトのキー(プロパティ)を列挙
for (let k in c) {
  console.log(k); // data1、data2
}
// オブジェクトをJSON文字列に変換
console.log(JSON.stringify(c)); // {"data":1,"data2":2}

SECRETプロパティの名前をシンボルとして準備し、「this[SECRET]=~」でプロパティとして定義

シンボルSECRETの値は他からは判別できませんので、SECRETプロパティに直接アクセスすることはできません。for...in命令による列挙、JSON.stringifyメソッドで生成されたJSON文字列にも、シンボルで生成されたプロパティは現れてこない点に注目してください。

ただし、完全に隠ぺいできるわけではなく、getOwnPropertySymbolsメソッドを利用すると、シンボルプロパティにアクセスすることは可能です。

let idsym = Object.getOwngetOwnPropertySymbols(c)[0];
console.log(c[idsym]);

配列/オブジェクトから個々の要素を抽出する ー 分割代入

分割代入(destrucruting assignment)は、配列/オブジェクトを分解し、その要素/プロパティを個々の変数に展開するための構文

let [hoge, foo] = [15, 21];
console.log(hoge); // 15

「...」演算子を利用することで、残りの要素をまとめて配列として取り出すこともできます。

let [hoge, foo, ...other] = [10, 20, 30, 40, 50];
console.log(hoge); // 10
console.log(other); // [30,40,50]

オブジェクトのプロパティに割り当てることもできます。

let {hoge, foo} = {hoge:'ほげ', foo:"ふ~"};
console.log(hoge); // ほげ

入れ子になったプロパティに割り当てる

let data = {hoge: 'ほげ', foo: {piyo: 'ぴよ', goo: 'ぐう'}};
let {hoge, foo, foo: {piyo, goo}} = data;
console.log(hoge); // ほげ
console.log(foo); // {"piyo":"ぴよ","goo":"ぐう"}

指定されたプロパティが存在しなかった場合の為に、デフォルト値を用意も可能です。

let {hoge = 'ほげ', foo} = {foo: 'ふ~'};
console.log(hoge); // ほげ
宣言のない代入

今までは、宣言と代入をまとめて行っていますが、当たり前なんですけど変数宣言と代入は別々に行えます。

let hoge, foo;
[hoge, foo] = [15, 21];

ただし、オブジェクトの分割代入では、前後に()が必須です。なぜなら、左辺の{...}はブロックと見なされ、それ単体で文とすることができないからです。

let hoge, foo;
({hoge, foo} = {hoge:'ほげ', foo: 'ふ~'});

長くなってきたので、今回はここまで区切ります。

次回もES6について書いていきます。
(関数あたりのところを)