旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section5 ~ES2015文法を覚えよう(前編)~

  • 386
    いいね
  • 2
    コメント

はじめに

この記事は「旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門」の5つ目の記事です。

シリーズの最初から読みたい方は
旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section1 ~すぐにでも現代っぽく出来るワンポイントまとめ~
へどうぞ。

また、このシリーズではECMAScript5を概ね対応するブラウザを対象としています。

もっと平たくいうと、IE8以下は切り捨てます。ご了承ください。

このシリーズを通して、原則として厳密さよりも分かりやすさを優先するためこのようにします。予めご了承ください。

目次

Section5 ~ES2015文法を覚えよう(前編)~

言語は日々変化し続けています。JavaScriptも、同様に変化を続けています。

Section4までの説明では、皆さんがよく知っているJavaScriptの文法を利用してすべての内容を説明してきましたが、Section5からは :sparkles: 新しいJavaScript :sparkles: を学んでいきます :blush:

:warning: 新しいJavaScriptは、新しいが故に、現行ブラウザでほとんど実装が出来ていません。

天下の :sparkles: GoogleChrome様 :sparkles: であっても、3月(もうすぐですね)から一般ユーザー向けに自動アップデートが配信されるChrome49になるまでは、ほとんど動かないはずです :confounded:

しかし安心してください、その点の対策は講じています :smile:
対策をしっかりすればIE9までなら理論上動かすことが出来ます :grin: :thumbsup: :sparkles:

具体的な対策方法については、後のセクションでお話していく予定なので、この章ではひとまず安心して文法を覚えていきましょう :blush:

ECMAScriptについて

今まで説明を避けてきましたが、流石にこの話になると避けて通れないのがECMAScriptという存在。

元々、JavaScriptはネットスケープっていう今は亡きブラウザに搭載されていた言語でした。

その後、皆さんご存知のInternetExplorer(IE)が続けざまにJScriptというJavaScriptのパクり親戚となる言語を搭載するようになりました。

なんとなく察しがつくかと思いますが、この両者、共通する部分もいくつか有りますが、基本的には互換性がありません :scream:

各ブラウザごとで動くスクリプトが違っては、開発サイドとしては非常に困りますよね :worried:

そういう歴史的経緯があって、ブラウザ間で差異のない共通仕様を策定しようという動きが生まれました。
そして誕生したのが :sparkles: ECMAScript :sparkles: と呼ばれるものです。
また、ECMAScriptを略して「ES」と呼ぶことも有ります。

因みに我々が指す場合には「ECMAScriptに記述されている仕様(書)」のことを指すことがほとんどです。

ECMAScriptはWなんとかpediaには「スクリプト言語である」なんて書いてます。
が、基本的にESをスクリプト言語として話すことは、ほぼないです :no_good:
(というかスクリプト言語って言い切っちゃって良いのか…?仕様(書)って言ったほうが良くないか…?)

ECMAScriptのバージョンについて

当然ですが、ネットスケープが生きていた時代からECMAScriptが全く仕様改定されなかったなんてことはありません。何度か改定されています :stuck_out_tongue_winking_eye:

因みに皆さん大嫌いのIE8はES3相当です。バージョン番号が「3」の「ECMAScript」なので「ES3」です。

この記事ではES5相当のブラウザを主に扱ってきたのですが、ここからは :sparkles: ES6 :sparkles: の話をしていきます。

あと、(個人的には嫌いなのですが)ES6以降は、毎年バージョニングする方針に切り替わったため、ES6のことを「ES2015」と呼ぶことがあります。
というか本家的にはこちらの呼び方を推奨しているようです。

というわけで!ちょっと前置きが長くなりましたがES2015の全文法をまとめていきたいと思います :laughing:

ES2015関連のネットの情報をチラチラと見る感じでは、単に文法だけを淡々とまとめている物が多い印象を受けました。
差別化を図るために、この記事ではテクニカルな部分も多めに含めていきたいと思います :sunglasses:

(余談)ES7(ES2016)以降の話について

つい先日ES7(ES2016)の仕様の方も確定しました。
が、話が長くなりそうなのでこのセクションでは触れません :no_good:
ES7の話については、絶賛策定中のES8(ES2017)以降の話と機会を見ながら、まとめて話しようと思います。

末尾再帰最適化(2016/04/11 06:45 編集)

処理を繰り返したい時、for文などのループ文を使ったり、Array.prototype.map等々のArrayについてる関数を利用する以外に、関数を再帰的に呼び出して実現する方法がありますよね。

再帰による階乗計算
console.log("3!の値は"  + fact(3)  + "です"); // "3!の値は6です"
console.log("10!の値は" + fact(10) + "です"); // "10!の値は3628800です"

function fact(n){
    return (n) ? n*fact(n-1) : 1;
}

再帰呼び出しは繰り返し処理をシンプルに書ける反面

  1. スタックオーバーフローのリスクがある
  2. 関数呼び出しのオーバーヘッドがあり、低速

といった問題が有ります :fearful:

ところで、再帰処理の記述は、「関数の一番最後の行でreturnする再帰については、必ずfor文等のループ文に書き換えられることが出来る」ということを、計算機数学のえらい人が証明しています :relaxed:
(※厳密ではない説明ですが石投げないでください)

そこで、ES2015に対応したJavaScriptでは、(コードを書く人間側が)ひと手間加えて再帰で階乗計算コードを記述した時に、(コンパイラ側が)滅茶苦茶頑張ってループ文の形に変換して処理してくれます :two_hearts:

ここでいう一手間とは、ざっくりとですが、returnで演算やらが一切無い記述にすることです。

再帰による階乗計算(returnをシンプルな形に)
console.log("3!の値は"  + fact(3)  + "です"); // "3!の値は6です"
console.log("10!の値は" + fact(10) + "です"); // "10!の値は3628800です"

function fact(n){
    return innerFact(n, 1);

    function innerFact(n, sum){
        return (n) ? innerFact(n-1, n*sum) : sum;
    }
}

これをコンパイラが頑張って変換すると以下のようになります(※イメージです)

コンパイラが頑張って変換してくれた後のコード
console.log("3!の値は"  + fact(3)  + "です"); // "3!の値は6です"
console.log("10!の値は" + fact(10) + "です"); // "10!の値は3628800です"

function fact(n){
    var ans = 1;
    while(n){
        ans *= n;
        n = n-1;
    }
    return ans;
}

たったそれだけのことなんですが、再帰を書いてるつもりなのに、実際はループ文として処理されているので、スタックオーバーフローのリスクから生存することが出来ます :heart:

let/const

今まで変数宣言は「var」を主に使っていましたが、varは今後一生使わなくても困りません。
それぐらいletとconstが便利です。

let/constの共通の性質としては

  1. 変数を宣言する時に書く。平たく言えば今までvarとしていた場所に書く
  2. ブロックスコープを持つ。平たく言えば関数以外の中括弧でもスコープを持つ
  3. 巻き上げない。平たく言えば宣言よりも前で変数を参照できない(strict modeでエラー)
  4. ブロックで束縛される。平たく言えばsetTimeoutをfor文で回しても闇らない(後述)

です。そして、letとconstの違いは、「宣言後に再代入可能かどうか」程度の違いしかありません。

letとconstによる変数宣言のサンプル
(function(){
    "use strict";
    var   a = 0; // 従来のvar宣言
    let   b = 1; // letによる宣言
    const c = 2; // constによる宣言
 // const d; // 宣言のみのconstは基本的にしない(strict modeではエラーが出ます。)

    var   a = 3; // 一度宣言した変数を再度宣言できる
 // let   b = 4; // letは一度宣言した変数を同一スコープで再宣言できない(strict modeではエラーが出ます。)
 // const c = 5; // constは一度宣言した変数を同一スコープで再宣言できない(strict modeではエラーが出ます。)

    a = 6; // varは再代入が出来る
    b = 7; // letは再代入が出来る
 // c = 8; // constは再代入が出来ない(strict modeではエラーが出ます)

    {
        var   a = 9;  // 当然だけど↑で宣言したaを上書きする
        let   b = 10; // このブロック(中括弧)内のみで有効なbを宣言する
        const c = 11; // このブロック(中括弧)内のみで有効なcを宣言する

        console.log(a); // 9
        console.log(b); // 10
        console.log(c); // 11
    }
    console.log(a); // 9
    console.log(b); // 7
    console.log(c); // 2

    // var宣言だと宣言前でも参照できる(巻き上げられる)
    console.log(e); // undefined
    var e = 12;
    // letやconstでは宣言前は参照できない(strict modeではエラーが出ます)
 // console.log(f);
 // let   f = 13;
 // console.log(g);
 // const g = 14;



    // varだと1秒後に3回「3」を表示してしまう
    for(var i=0;i<3;++i){
        setTimeout(function(){
            console.log(i);
        },1000);
    }

    // letだと1秒後に「0」「1」「2」を一気に表示する
    for(let i=0;i<3;++i){
        setTimeout(function(){
            console.log(i);
        },1000);
    }
})();

見て分かるように、基本的にこの2つはstrict modeでないとあまり恩恵が得られません。
絶対に「"use strict";」しましょう :blush:
(strict modeを忘れた方はSection1を再度読んでください)

(補足) :sparkles: const :sparkles: 信者になろう

――constはいいぞ、心が豊かになる

テクニカルですが、上記の性質を利用して、strict mode下で全てをconstにすれば、スコープを超えた意図しない再代入を阻止できます。

constによるスコープを超えた再代入防止
(function(){
    "use strict";

    // letとconstでそれぞれ適当に変数を宣言する
    let   name = "gao";
    const age  = 18;

    // 処理が複雑になって、内部で入れ子関数を実行したとしましょう。
    (function(){
        name = "";     // しまった!外側のスコープの値を書き換えてしまった!
     // age  = 18;     // constなら再代入できないからエラー吐いて気づけるね☆
     // nyaa = "ねこ"; // そもそも外側スコープにない変数はReferenceエラー吐くから気づけるね☆
    })();

    // †どこで消えたんだ私の名前は…(絶望)†
    console.log(name); // ""

    // ☆constなら絶対に消えない安心感があるね☆
    console.log(age); // 18
})();

というわけで、実はコード中にvarやletやグローバル変数が増えれば増えるほど、スコープ間での上書きリスクが増えてしまうのです :scream:

現実問題では、性能やその他どうしても破壊的に処理しないといけない場面は存在します。
したがって、100% constだけでコードを書くことは不可能です :sweat:

しかし、大部分の変数に関してはconstにすることが可能です :heart:
体感では1つのプログラムで9割ぐらいはconst、残り1割をletといった感じですね。

実際に、以降の説明ではconstとletを主に使っていくので、ちょっと意識して読んで見てください。
constの出現頻度が、letに比べて遥かに高いことが分かりますので :wink:

:sparkles: アロー関数 :sparkles:

アロー関数は無名関数を簡略記述です。しかし、完全なシンタックスシュガーというわけではなく、一部異なる部分もあります。

基本記述は「(引数)=>{ 処理; }」です。
また、「(引数)=>{ return 処理; }」は「(引数)=>(処理)」と書くことも出来ます :sparkles:

アロー関数の例
// 従来の無名関数の書き方
var f = function(a,b){
    return a + b;
};
console.log(f(2,4)); // 6

// アロー関数による書き方1
const g = (a,b)=>{
    return a + b;
};
console.log(g(2,4)); // 6

// アロー関数による書き方2
const h = (a,b)=>a+b;
console.log(h(2,4)); // 6

// 従来の即時実行
(function(){
    console.log("がお、さんじゅうはっさい");
})();

// アロー関数による即時実行
(()=>{
    console.log("がおさん、じゅうはっさい");
})();

本質的にはこれで説明が終わります。(簡単? 簡単!)

:warning: アロー関数は大変便利なのですが、注意すべき点が主に3点あります。

1点目はアロー関数はargumentsオブジェクトを有していません :exclamation:

アロー関数はargumentsを持ってない話
// 従来の関数はargumentsを持っていた
(function(){
    console.log(typeof(arguments)); // "object"
    console.log(arguments.length);  // 6
    console.log(arguments[2]);      // 4
})(1,1,4,5,1,4);

// アロー関数はargumentsオブジェクトを持っていない!
(()=>{
    console.log(typeof(arguments)); // "undefined"
})(1,1,4,5,1,4);

この仕様で主に困るのは可変長引数ですが、これは後述のrest parameterで対処が出来ます。
(というかrest parameterがあれば従来の関数宣言においてもargumentsは使う必要ほぼないです)

そしてもう1点が、thisがレキシカルに決定されるという点です。
レキシカルってなんぞ :flushed: って感じる方は、
「書いた位置まんまでthisが決定する」
って読み替えてもおおよそ間違いではありません。

アロー関数におけるthisの扱い
(function(){
    "use strict";

    // 適当な変数を宣言する(説明用)
    const age = 18;

    // thisにはwindowオブジェクトが入っている
    console.log(this); // [object Window]

    const f = ()=>{
        // 変数の場合は、この関数内になければ、一つ外側の関数の値を参照するので
        // ひとつ上の関数で宣言されたageが参照されて、「18」が表示される。
        console.log(age); // 18

        // アロー関数のthisも、変数と全く同様に考える。
        // この関数について、thisが束縛されていないため、本来はこのthisはundefinedになる。
        // 変数の場合と同様に、thisがこの関数内に(束縛されて)なければ、
        // ひとつ上の関数で指定されたthisが参照されて、「object Window」が表示される。
        console.log(this); // [object Window]
    };
    f();

    const g = function(){
        // ひとつ上の関数で宣言されたageが参照されて、「18」が表示される。
        console.log(age); // 18

        // 通常の関数の場合は、thisはダイナミックに決定される。
        // この関数gについて、特にthisがbindされていないため
        // (strict modeでは) thisはundefinedになる。
        console.log(this); // undefined
    }
    g();
}).call(window); // thisを「windowオブジェクト」にする

そして最後3点目が、恋のABC :couplekiss: が適用できないという点です。

・・・?

えっ!:eyes:

もしかして、もしかしてですが

何年間にも渡って全JavaScript界を震撼させて続けている「恋のABC」を存じあげないと申すのですかあぁぁあぁあ!?!?:flushed: :laughing: :flushed: :laughing: :fist: :v: :raised_hand:

・・・っていう茶番は置いといてですね。

我々の業界では、Function.prototypeにあるプロパティ

  • .apply()
  • .bind()
  • .call()

の関数の頭文字を取って、俗にJavaScriptの恋のABCと呼んでいます。
どこの業界だそんな業界は燃やしてしまえ :fire: :fire: :fire:

あ、因みに半分は冗談です。半分は本当ですけど。

さて、先ほどアロー関数のthisはレキシカルに決定されると書きましたが、これは恋のABCを用いてもなお変わらない事実です。
アロー関数は常にレキシカルに決定されるのです。

アロー関数における恋のABCの扱い
(function(){
    "use strict";

    // thisにはwindowオブジェクトが入っている
    console.log(this); // [object Window]

    // 関数fはapplyしてみる(thisは適当なオブジェクトを渡す)
    const f = ()=>{
        // なんとapplyの値を無視して、ひとつ上のスコープのthisを取ってくる!
        console.log(this); // [object Window]
    };
    f.apply({name: "gao"});

    // 関数gはbindしてみる(thisは適当なオブジェクトを渡す)
    const g = (()=>{
        // なんとbindの値を無視して、ひとつ上のスコープのthisを取ってくる!
        console.log(this); // [object Window]
    }).bind({name: "gao"});
    g();

    // 関数hはcallしてみる(thisは適当なオブジェクトを渡す)
    const h = ()=>{
        // なんとcallの値を無視して、ひとつ上のスコープのthisを取ってくる!
        console.log(this); // [object Window]
    };
    h.call({name: "gao"});
}).call(window); // thisを「windowオブジェクト」にする

因みにこの仕様が痛烈に効いてくるのが、実はみんな大好きjQueryです(ナ、ナンダッテー)

jQueryイベントにおけるthisは
「セレクタで対象となっている、発火対象のオブジェクトのHTMLElement」
が入っているのですが、それの実装がapplyなので、適用できないのです。

jQueryとアロー関数は相性最悪っていう話
// 気をつけるべきは、jQuery(function(){ ... }); という書き方は
// $(document).on("ready",function(){ ... }); の省略記法であるということです。
jQuery(function($){
    "use strict";

    $("#hoge").on("click",function(eve){
        // jQuery内部で、イベントを発火する時にapplyしている
        console.log(this); // idが「hoge」のHTMLElementが入る。
    });

    $("#hoge").on("click",(eve)=>{
        // イベントを発火する時のapplyに対して、アロー関数は無視してしまうのだ!
        console.log(this); // ひとつ上の$(document).on("ready")のコールバック関数がもつdocumentオブジェクトが入る
    });
});

※因みに、jQuery1.3以降では、eve.currentTargetというのが存在します。これがthisと等価です。

jQueryイベントを登録するときには、従来の関数を使用するか、eve.currentTargetを利用するかの二択です。好きな方をどうぞ:confounded:

(補足)アロー関数のthis束縛のもうちょっと踏み込んだ話

アロー関数のこのthisの性質は、良い方向にも悪い方向にも働きます。
良い例、悪い例をそれぞれ出してみましょう :expressionless:

アロー関数のthis束縛特性における良い方向の例と悪い方向の例
(function(){
    "use strict";

    // 適当にクラス(っぽい関数)を定義します
    function Human(firstName,lastName,symbol){
        this.firstName = firstName;
        this.lastName  = lastName;
        this.symbol    = symbol || "☆";
    }

    // 悪い方向に働く例
    Human.prototype.getFullNameBad = ()=>{
        // ここにおけるthisは、bindされていないためundefinedになる。
        // 一つ外側の関数で、thisがwindowオブジェクトに束縛されているため、
        // この関数におけるthisは [object Window] になってしまう…(最悪)
        console.log(this); // [object Window]

        return this.firstName + " " + this.lastName;
    };

    // ↑を動かしたい場合は今まで使ってきたfunction文を利用する
    Human.prototype.getFullNameGood = function(){
        // ここにおけるthisは、newした時に作られるオブジェクトに束縛されているため、 
        // [object Human] になる
        console.log(this); // [object Human]

        return this.firstName + " " + this.lastName;
    };

    // 良い方向に働く例
    Human.prototype.getFullNameSymbol = function(){
        const name = this.getFullNameGood(); //フルネームを取得する

        return name.replace(/\s/,($0)=>{
            // ここにおけるthisは、本来はundefinedだが
            // 一つ外側の関数で、thisがnewした時に作られるオブジェクトに束縛されているため、
            // そちらを参照して [object Human] になってくれる♡
            console.log(this); // [object Human]

            return this.symbol;
        });
    };

    // インスタンスの生成
    const cocoa = new Human("心愛","保登");

    cocoa.getFullNameBad(); // "undefined undefined" (※windowオブジェクトにfirstNameおよびlastNameプロパティが定義されていない場合の例です)
    cocoa.getFullNameGood(); // "心愛 保登"
    cocoa.getFullNameSymbol(); // "心愛☆保登"
}).call(window); // thisを「windowオブジェクト」にする

こんな感じで、thisを意識しながら使い分けが必要になってきます。thisが関与するコードに触れる時は意識してみてください。

なお、アロー関数を上手に利用すれば

黒魔術
var _this = this;

という旧石器時代の :poop: 黒魔術 :poop: を利用する機会が激減します :sparkles:
(※以前でもFunction.prototype.bindを利用出来たので、この黒魔術利用の機会は減っていましたが、高速化のためにしばしば詠唱されていました :thumbsdown: )

thisが絡んでくるとややこしくなりますが、以上をよく整理しておきましょう。
これをちゃんと理解してない場合、しばしばバグの元になります :bug: :punch: :boom:

クラス構文

基本的にJSのクラスは旧来の「prototypeチェーンによるクラスもどきの作成」でやってることのシンタックスシュガーなので、旧来のクラスもどきとほぼ同等の操作ができます。

(さらに言えば、その実体があくまでもクラスもどきなので、旧来の継承方法を頭の中にイメージしながらclass構文を記述していかないと、扱いが難しいと思います) :cold_sweat:

旧来のprototypeチェーンによる継承は、以下の記事がとても良くまとまっているので、オススメです。理解してない方は以下の記事等で復習しておいてください。
[JavaScript] そんな継承はイヤだ - クラス定義 - オブジェクト作成

↑の記事を完全に頭に叩き込んだ状態でなら、次の記事が読めると思います。
Class構文について - JS.next

この記事が大変良くまとまっているので、私の方からのクラスについての説明は省略する形にします。

(補足)クラスとprototypeの合わせ技をキメる

くどいようですが、JSのクラスはあくまでシンタックスシュガーなので、クラスで宣言したものにprototypeを生やすことが出来ます。

クラスにprototypeを生やす
// どうでもいいけど皆さんトビウオって食べたことあります?
// 骨は多いですが美味しいんですよ。オススメです。
class FlyingFish{
    constructor(){}
    fly(){
        return "ぴょんぴょんっ";
    }
}

// あくまでクラス型というのはなく、その実体は関数である。
console.log(typeof(FlyingFish)); // "function"

// クラスに対してprototypeを生やす事もできる
FlyingFish.prototype.swim = function(){
    return "すいすいっ";
};

// インスタンスの生成
const flyingFish = new FlyingFish();

// クラスで定義したメソッドは普通に呼べる
flyingFish.fly(); // "ぴょんぴょんっ"

// prototypeで定義したメソッドも呼べる!
flyingFish.swim(); // "すいすいっ"

この性質を知っておくと、法則性のあるメソッドをまとめて定義する時にめちゃくちゃ便利です :sparkles:

BinaryReader.js
// 土台はクラスで定義しておく
class BinaryReader{
    constructor(buff,opt){
        opt = opt || {};

        this.offset         = opt.offset || 0;
        this.isLittleEndian = !!opt.isLittleEndian;
        this.dataView       = new DataView(buff,this.offset,opt.length);
    }

    // (本来はprototypeの書き方とまとめるのですが)今回はprototypeとの比較用に
    // 浮動小数点型の読み込みメソッドについては、あえてclassの記法に従って定義してみます。

    readAsFloat32(){
        // 指定された位置のdataViewの値を読み取って、読み取ったバイト数だけ読み取り位置を進める
        const value = this.dataView.getFloat32(this.offset, this.isLittleEndian);
        this.offset += (32>>>3);

        return value;
    }

    readAsFloat64(){
        // 指定された位置のdataViewの値を読み取って、読み取ったバイト数だけ読み取り位置を進める
        const value = this.dataView.getFloat64(this.offset, this.isLittleEndian);
        this.offset += (64>>>3);

        return value;
    }
}

// 仮組みで作ったクラスに対して、規則性のあるメソッドについては
// prototypeを利用して、まとめて定義処理をする
[8,16,32]
    .forEach(bit=>{
        ["Int","Uint"]
            .map(type=>type+bit)
            .forEach(method=>{
                // 動的に生み出したメソッド名を使ってdefinePropertyでまとめて定義する
                Object.defineProperty(BinaryReader.prototype,"readAs"+method,{
                    value: function(){
                        // 指定された位置のdataViewの値を読み取って、読み取ったバイト数だけ読み取り位置を進める
                        const value = this.dataView["get"+method](this.offset, this.isLittleEndian);
                        this.offset += (bit>>>3);

                        return value;
                    },
                });
            });
    });

// モジュールとして出力する
module.exports = BinaryReader;
index.js
// モジュールを読み込む
const BinaryReader = require("./BinaryReader.js");

// バッファーを用意する
const typedArray  = new Uint8Array([1, 1, 4, 5, 1, 4, 8, 1, 0, 1, 9, 1, 9]);
// 先ほど作ったリーダークラスのインスタンスを生成する
const binaryReader = new BinaryReader(typedArray.buffer, { isLittleEndian: false });

// BinaryReaderクラス側で各種readAs○○系メソッドをprototypeを用いて定義したので、
// 以下のようなメソッド各種を呼び出すことが出来る!

//  o
// [1, 1, 4, 5, 1, 4, 8, 1, 0, 1, 9, 1, 9]
console.log(binaryReader.readAsInt8());    // 0x01       => 1

//     o  o
// [1, 1, 4, 5, 1, 4, 8, 1, 0, 1, 9, 1, 9]
console.log(binaryReader.readAsInt16());   // 0x0104     => 260

//           o  o  o  o
// [1, 1, 4, 5, 1, 4, 8, 1, 0, 1, 9, 1, 9]
console.log(binaryReader.readAsUint32());  // 0x05010408 => 83952648

//                       o  o  o  o
// [1, 1, 4, 5, 1, 4, 8, 1, 0, 1, 9, 1, 9]
console.log(binaryReader.readAsFloat32()); // 0x01000109 => 2.351062970463184e-38

//                                   o  o  x  x  x  x  x  x
// [1, 1, 4, 5, 1, 4, 8, 1, 0, 1, 9, 1, 9]
console.log(binaryReader.readAsFloat64()); // Error: Offset is outside the bounds of the DataView

index.jsの方を見ていただければだいたいわかると思いますが、ビット幅を指定してArrayBufferを読み込むためのクラスですね。

JSらしさ全開のメタメタしいメソッド定義ですね :sunglasses:
私は変態なのでこういうの好きです :heart:

:warning: ArrayBuffer/ArrayBufferView(TypedArrayとDataViewの総称)はIE9では非対応ですが、そもそもこいつらは名目上はES2015なので、この記事では対応状況を考慮しません。

※ definePropertyやArrayBufferやArrayBufferViewの使い方について把握していない方は、MDN等でチェックしましょう。

(:sunglasses:プロ向けの仕様)

(:beginner:ゆるふわ向けオススメサイト)

※それと心底どうでもいい話ですが、ArrayBuffer/ArrayBufferViewに関して、ES2015以降はnewを付けることが強要されるように仕様変更が行われました。
以前からnewを付けていた方は別に気にしなくていいです。一応気をつけてください。

Symbol型

NumberやStringやBooleanやFunctionのような各種型に加えて、新たに「Symbol型」が出来ました :laughing:

Symbol型は、Symbol()という関数で生成することが出来ます。そして、1度生成したシンボルは再生成することが出来ません。

Symbolの例
const a = Symbol();
const b = Symbol();
const c = a;

// aとbは、それぞれ違う値として認識される。
console.log(a === b); // false

// また、cはaのエイリアスとして認識される。
console.log(a === c); // true

これは、簡易的には以下のようなものだと考えると、人によってはイメージしやすいかもしれません。

シンボル型の簡易的な実装
const a = Symbol();
const b = Symbol();
const c = a;

console.log(a === b); // false
console.log(a === c); // true

// 大雑把には空のオブジェクトを返してるようなもんだと思ったら幸せです。
function Symbol(){
    return {};
}

とはいえこのSymbol型の説明では、実際の実装とは大きく異なります。
あくまでも初学段階 :beginner: のイメージとして捉えるのに留めておいてください

(初学向けではなく、実際の)ES2015 Symbolの実装では、Symbolはオブジェクトのプロパティとして使用できます。

Symbolの例
// プロパティ用のシンボルを作る
const prop = Symbol();

const obj = {};

obj[prop] = "オレオレシンボルプロパティ";
obj.prop  = "これは通常のプロパティ";

console.log(obj[prop]);   // "オレオレシンボルプロパティ"
console.log(obj.prop);    // "これは通常のプロパティ"
console.log(obj["prop"]); // "これは通常のプロパティ"

その他マニアックなことについては、各種記事の情報が分かりやすかったのでそちらをチェックする形とします。
Symbol - MDN
Symbolについて - JS.next
ECMAScript6にシンボルができた理由

この中でも、以降の話で特に重要なのは、ビルトインシンボルの話です。
上記のうち、MDNとJS.nextに書いてます(JS.nextがオススメ)

若干ネタバレですが、Symbol.iterator というプロパティ (名称としては@@iterator) というものが、
ブラウザ(などの処理系)側で既に用意されているということだけは把握しておいてください :star:

@@iteratorプリミティブSymbol
console.log(Symbol.iterator); // Symbol(Symbol.iterator) ←これがある

:sparkles: オブジェクト拡張記法 :sparkles:

超便利なのでタイトルキラキラさせました。これは絶対覚えましょう。

オブジェクト操作周りの既存の書き方に対して、様々な省略記法が可能になりました。
これに関しては百聞は一見にしかずみたいなところがあるので、順に書いていきます。

変数のキーでオブジェクトの初期化が出来るようになった
const key = "name";

// 今までの書き方
var objA = {};
objA[key] = "条河麻耶";
console.log(objA.name); // "条河麻耶"

// 省略記法
const objB = {
    [key]: "奈津恵",
};
console.log(objB.name); // "奈津恵"
プロパティのキー名と変数値が一緒の時はコロンで挟む必要が無くなった
const age = 18;

// 今までの書き方
var objA = {"age": age};
console.log(objA.age); // 18

// 省略記法
const objB = {age};
console.log(objB.age); // 18
プロパティの値が関数の時は楽に書けるようになった
// 今までの書き方
var objA = {
    add: function(a,b){
        return a+b;
    }
};
console.log(objA.add(2,4)); // 6

// 省略記法1(※アロー関数の省略記法ではないので、thisの対象には注意)
const objB = {
    add(a,b){
        return a+b;
    }
};
console.log(objB.add(-1,4)); // 3

// 省略記法1の補足
// アロー関数を書きたい時はアロー関数自体が省略記法なので、
// 今までどおりのオブジェクトの書き方に対して、アロー関数を生やせばOKです。
const objC = {
    add: (a,b)=> a+b
};
console.log(objC.add(5,8)); // 13

// 省略記法2(あまり使われませんが文字列キーでも同様です)
const objD = {
    "add"(a,b){
        return a+b;
    }
};
console.log(objD.add(-1,-2)); // -3

// 省略記法3(当然ですが変数キーと関数省略を組み合わせることも出来ます)
const key = "add";
const objE = {
    [key](a,b){
        return a+b;
    }
};
console.log(objE.add(7,1)); // 8
プロパティのSetter/Getterが楽に書けるようになった
// 今までの書き方
var objA = {};
Object.defineProperty(objA,"age",{
    set: function(value){
        if(typeof(value)!=="number") throw new TypeError("ageはNumber型のプロパティです");
        this._age = value;
    },
    get: function(){
        return this._age;
    }
});
objA.age = 18;
console.log(objA.age); // 18

// 省略記法
const objB = {
    set age(value){
        if(typeof(value)!=="number") throw new TypeError("ageはNumber型のプロパティです");
        this._age = value;
    },
    get age(){
        return this._age;
    }
};
objB.age = 18;
console.log(objB.age); // 18

これら記法はとても便利なので、以降の説明にも度々出てくると思います :laughing:
忘れたらその度に読み返してください。これを全部暗記するだけで捗り方が段違いです。

:sparkles: 分割代入 :sparkles:

超便利なので(以下略)

分割代入は、複数の変数をまとめて代入するときの記法です :two_hearts:
主に配列やオブジェクトに対して行うことが多いです。

分割代入による配列代入
const arr = ["香風智乃","保登心愛","天々座理世","宇治松千夜","桐間紗路"];

// 今までの書き方
var a = arr[0];
var b = arr[1];
console.log(a); // "香風智乃"
console.log(b); // "保登心愛"

// 分割代入による書き方
const [c,d] = arr;
console.log(c); // "香風智乃"
console.log(d); // "保登心愛"

// 例えば[1]と[4]が欲しいという場合は、
// 対応する欲しい位置まで「,」を増やして位置を合わせる
const [,e,,,f] = arr;
console.log(e); // "保登心愛"
console.log(f); // "桐間紗路"
分割代入によるオブジェクト代入
const obj = {
    name: "がお",
    age:  18
};

{
    // 今までの書き方
    var name = obj.name;
    var age  = obj.age;
    console.log(name); // "がお"
    console.log(age);  // 18
}

{
    // 省略記法
    const {name,age} = obj;
    console.log(name); // "がお"
    console.log(age);  // 18
}

それと、分割代入は配列とオブジェクト以外にも、「iterable」なオブジェクトには全て適用できます。

詳しくはGenerator / Iteratorの説明をしてからにしますが、具体的には「String型」や、以降の話に出てくる「Mapオブジェクト」や「必要なメソッドを全て搭載したオレオレオブジェクト」などに適用できるという話です。

現時点ではかなり大雑把に「Symbol.iterator」というシンボルプロパティを持っているオブジェクトには適用できるという形の説明で留めておきます。

String型における分割代入
const str = "がおさんじゅうはっさい";

// ES2015以降、String型には[Symbol.iterator]というシンボルプロパティが追加された
console.log(str[Symbol.iterator]); // function [Symbol.iterator]() { [native code] }

const [a,,,b] = str; // strの0番目と3番目の文字を取り出して分割代入する

//  ○
// "がおさんじゅうはっさい"
console.log(a); // "が"


//        ○
// "がおさんじゅうはっさい"
console.log(b); // "ん"

分割代入の利用方法ですが、よくある例が変数のswapです

分割代入を利用した変数swap
let a = 1;
let b = 2;

// 旧石器時代の方法
var tmp = a; // ←一時変数がダサいヤバイ
a = b;
b = tmp;

// JavaScriptが大好きな人間によるES5以前の方法
a = [b,b=a][0]; // 記法が気持ち悪い

// 分割代入を利用した方法
[a,b] = [b,a]; // めっちゃシンプル!!!

しかし、swapなんて実用上はめったに使いません :disappointed:

実用上一番よく使う分割代入の例は、関数の多値返しです :blush: :sparkles:

分割代入による関数の多値返し
// 関数fを実行し、帰ってくるオブジェクトを分割代入で受ける
const {status,data} = f();

// 成功した時だけ結果を表示する
if(status) console.log(data);

// 関数fは 1/2 の確率で失敗する関数!
// 成功かどうかはstatusプロパティのboolean値で判定できるものとする
function f(){
    const status = !!(Math.random()*2|0);

    // 成功時の処理
    if(status) return {
        status,
        data: "やった成功したよ!",
    };

    // 失敗時の処理
    return {
        status,
        data: null,
    };
}

それと、match関数の受け手を分割代入すると、かなり捗るという話もあります :yum:

分割代入によるmatch関数の受け取り
const tweet = ".@gaogao_9 この人JS愛好家の変態です!";

// match関数を分割代入で受ける。
// match関数の仕様上、HITしなければnullを返すので、デフォルト値を空配列にしておく。
const [isMatch,user,text] = tweet.match(/@([a-z0-9_]+)\s+(.+)/) || [];

if(isMatch){
    console.log(user); // "gaogao_9"
    console.log(text); // "この人JS愛好家の変態です!"
}

それともう一つ、for..of構文を使うときにめちゃくちゃ便利という話もありますが、これはfor...of構文の話のところで話をします :kissing_smiling_eyes:

:poop: 分割引数 :poop:

ほぼ分割代入と話が同じなので深い話はしませんが、関数の引数も同様に展開することが出来ます。

分割引数の例
const retF = f([1,2,3,4,5]);
const retG = g({
    name: "がお",
    age: 18,
    gomi: "ゴミプロパティも添えておきますね",
});

console.log(retF); // 5
console.log(retG); // "こんにちはがおさん(18)"

// 配列引数を展開して0番目と3番目の値だけ取り出す分割引数
function f([a,,,b]){
    return a + b;
}

// オブジェクト引数を展開して、nameとageを取り出す分割引数
function g({ name, age }){
    return "こんにちは" + name + "さん(" + age + ")";
}

因みに分割引数はあんまり使いません。というか禁止しても差し支えないです。理由は3つあります。

  1. 分割引数で受け取る変数は、全てconstではないから
  2. 通常の引数として受け取っておいて、それを分割代入すれば擬似的にconstにできるから
  3. プロパティが増えると読みづらくなる(引数が多すぎる関数と同じリスクを抱えている)

要するに唯一神constと相性が悪いから、ってことですね :fearful:

理由の3つ目はおまけです。
人によっては3つ目の理由が重要かもしれませんが、const教においては3つ目は相対的におまけになります :innocent:

:star2: 展開演算子(スプレッド演算子) :star2:

たまに便利です。

[Symbol.iterator]をプロパティに持つオブジェクトに対して、配列を変数に展開します。
文字列だろうが配列だろうがなんでも出来るんですが、配列への使用がポピュラーだと思います。

記号は「...」です。半角ピリオド3回です。

展開演算子の利用例
const input = [0,[1,2,3],4,5,[6]];

// inputが気持ち悪い。平らにしたい。そうだ展開しよう(名案)
const outputA = [input[0], ...input[1], input[2], input[3], ...input[4]];
console.log(outputA); // [0,1,2,3,4,5,6]

// おまけですが、Array.prototype.reduceと相性が良いです。
const outputB = input.reduce((arr,item)=>(
  (item[Symbol.iterator]) ? [...arr, ...item] : [...arr, item]
), []);
console.log(outputB); // [0,1,2,3,4,5,6]

展開演算子のもう一つの利用例として、配列等々を関数の引数に渡して展開するという利用例があります :two_hearts:

Math.maxは複数の引数を受け取って、その中から最大の値を返す関数です。
配列の中から最大値を求める時、以前はapplyを使って闇々してましたが、それも今日でおさらばです。

展開演算子によるMath.maxの利用
const arr = [1,1,4,5,1,4];

// 今までの方法
var maxA = Math.max.apply(null,arr);
console.log(maxA); // 5

// これからの方法
const maxB = Math.max(...arr);
console.log(maxB); // 5

シンプルですね、人類が求めていた回答はこれですよ:ok_woman: :clap: :sparkles:

そして、なんと展開演算子を用いる記述法をすれば、ほぼ全てのapplyはcallに置き換えることが出来ます :innocent:
(恋のABCとはなんだったのか、これでは恋のBCではないか)

applyをcallに置き換える
const hoge = function(){ /* ...(いろいろ書いてる) */ };
const args = [ /* 引数色々書いてる */ ];

// (ほぼ)等価の2つです
hoge.apply(null, args);
hoge.call(null, ...args);

こうすれば、applyという存在を完全に忘れ去ることが出来ますね。

今までは、bindとcallは引数の形態が一緒だけど、面倒くさいことにapplyだけ第二引数の形態が違うので特別扱いする必要がありました :sweat_drops:

しかし展開演算子があれば話は別です。

applyの存在は、以降忘れてしまってもおおよそ差し支えないですね…。 \アッカリーン/

:star2: rest parameter(rest引数) :star2:

普段は不要ですが、必要な場面で狂おしいほど必要になります。

引数の足りない部分を表現します。
こいつも展開演算子と同じピリオド3個で表現します。
が、配列を変数に展開する展開演算子とは性質的に逆なので、ちょっと注意です :construction_worker:

rest引数の例
// 今までの書き方
var f = function(a,b){
  var rest = Array.prototype.slice.call(arguments,2);
  return "前2つの引数の和:" + (a+b) + ", 残った引数の長さ:" + rest.length;
};
console.log(f(1,1,4,5,1,4)); // 前2つの引数の和:2, 残った引数の長さ:4

// rest引数による書き方
const g = (a,b,...rest)=>{
  return "前2つの引数の和:" + (a+b) + ", 残った引数の長さ:" + rest.length;
};
console.log(g(1,1,4,5,1,4)); // 前2つの引数の和:2, 残った引数の長さ:4

旧石器時代には、argumentsオブジェクトが、

:poop: ArrayっぽいけどArrayじゃない :poop: オブジェクト

だったため、Array.prototypeを利用してエクストリームな闇を体感する必要がありました :scream:

しかしそれも今日でおさらばです :kissing_heart: :sparkles: :thumbsup: :thumbsup: :thumbsup:

rest引数さえあれば、可変長引数の関数を実装する際にも、argumentsオブジェクトに一切触れること無く直感的な記述ができます :heart:

これにより、アロー関数が抱える「argumentsオブジェクトを持たない」という問題が一挙に解決できることが分かりますね :star2:

:star2: デフォルト引数 :star2: (16/02/25 19:00 追記)

あると便利、程度ですね。

他の言語にも採用されてることが多く、他の言語経験者だと馴染み深いと思いますが、ついにJavaScriptにもデフォルト引数が実装されました。

これは、引数を省略したり、引数にundefinedを与えた場合に、デフォルトの値を引数に与える構文です。

デフォルト引数
(function(){
    "use strict";
    const undefined = void(0);

    // 今までの書き方
    var f = function(age){
        if(age === undefined) age = 18;

        return "年齢: " + age + "さい";
    };
    console.log(f());          // "年齢: 18さい"
    console.log(f(6));         // "年齢: 6さい"
    console.log(f(undefined)); // "年齢: 18さい"
    console.log(f(null));      // "年齢: nullさい"

    // デフォルト引数による書き方
    const g = (age=18)=>{
        return "年齢: " + age + "さい";
    };
    console.log(g());          // "年齢: 18さい"
    console.log(g(6));         // "年齢: 6さい"
    console.log(g(undefined)); // "年齢: 18さい"
    console.log(g(null));      // "年齢: nullさい"
})();

:warning: ただし、デフォルト引数よりも後には、デフォルト引数やrest引数以外は定義できない点は気をつけましょう。

デフォルト引数の細かい挙動
// ↓のように、デフォルト引数ageの後に、通常引数のnameを与えることは出来ません。
function badDefaultParameters(age=18,name){
    return name + "さん(" + age + ")";
}

// 例外的に、デフォルト引数の後にはデフォルト引数やrest引数は与えることが出来ます。
function goodDefaultParameters(age=18, name="gao", ...rest){
    return name + "さん(" + age + ")";
}

console.log(goodDefaultParameters());          // "gaoさん(18)"
console.log(goodDefaultParameters(38));        // "gaoさん(38)"
console.log(goodDefaultParameters(38,"がお")); // "がおさん(38)"

その他細々とした挙動の話があるのですが、瑣末なことなので全部MDNに丸投げします。
Default parameters - MDN

(補足) 分割引数へのデフォルト引数の適用

const教には関係ない話ですが:poop: 分割引数 :poop: にも当然適用できます。

分割引数にデフォルト引数を適用する例
const f = ({age=18})=>{
    console.log("年齢: " + age + "さい");
};
try{
    f({ age: 5 });  // "年齢: 5さい"
    f({});          // "年齢: 18さい"
    f();            // 例外発生!
}
catch(err){
    // Error: Cannot read property 'age' of undefined
    console.error(err.message);
}

// その2: 分割引数の元になるオブジェクトにデフォルト引数を適用する例
const g = ({age} = {})=>{
    console.log("年齢: " + age + "さい");
};

g({ age: 5 });  // "年齢: 5さい"
g({});          // "年齢: undefinedさい"
g();            // "年齢: undefinedさい"

// その3: 分割引数自体と、その元になるオブジェクト両方にデフォルト引数を適用する例
const h = ({age=18} = {})=>{
    console.log("年齢: " + age + "さい");
};

h({ age: 5 });  // "年齢: 5さい"
h({});          // "年齢: 18さい"
h();            // "年齢: 18さい"

// その4: その3の派生で、元になるオブジェクトのプロパティに値を予め入れておく例
const i = ({age=18} = {age:100})=>{
    console.log("年齢: " + age + "さい");
};

i({ age: 5 });  // "年齢: 5さい"
i({});          // "年齢: 18さい"
i();            // "年齢: 100さい"

それぞれ細かい挙動の違いがありますが、原則を理解していれば悩むことはないと思います。


というわけで、Section5についての内容はここまでとなります。
ES2015の全仕様の大体1/3ぐらいは話せたかなーと思います。

Section6からも引き続き残りの仕様について話していきたいと思います。

Section6以降は現在執筆中です。記述が完了次第、こちらの記事からもリンクできるように致します。お待ち下さい。