ES2015の構文復習

Javascript Apr 22, 2020

ES2015~2019で追加された構文や機能をまとめてみます。

JavaScriptの標準化とバージョン

JavaScriptの誕生は、その生みの親であるブレンダン・アイクがNetscape1で動作するスクリプト言語を作るよう頼まれたことから始まります。

言語が完成したあと、Netscapeのマーケティングチームは、マーケティングの一貫としてその言語に「JavaScript」と名付ける許可を、当時Javaの商標を持っていたサン・マイクロシステムズ社から取り付けました。数年すると、IEがブラウザ市場を席巻するようになり、Netscapeプロジェクトは閉鎖されることになりました。

Microsoft IEでは、Javascriptを模倣したJScriptなどの独自言語を実装しはじめました。しかし、多数のブラウザが独自の実装を始めたことで、こっちのブラウザでは動くのにあっちのブラウザでは動かないという状況に陥りました。

これを解決するために標準化団体の「欧州電子計算機工業会、European Computer Manufacturers Association」により、JavaScriptの仕様が標準化され、団体名の頭文字をとってECMAScriptと名付けられました。後に、標準化団体は1994年にその国際的な立場を反映して「ECMAインターナショナル」に改名しています。

しばらくして、アイクのリードのもとFirefoxが開発され、GoogleからはChromeも発表されると、IEの独占市場が崩れはじめました。その後、2009年にはECMAScript 5がリリースされ、ECMAScriptは多くのブラウザがJavaScriptを実装するにあたって準拠する標準としての地位が確立しました。そして、2015年にリリースされたECMAScript 6はBabelなどのトランスパイラ(ECMAScript 6で書いたコードを5以前のバージョンのものに変換するプログラム)の後押しもあって、大きく成功することになりました。この第6版以降はバージョンとして発行年が使われるようになりました。つまり、第6版はECMAScript 2015と呼ばれます。また省略してES2015とも呼ばれます。

ES2015以降、仕様は毎年改訂されることが決められ、2019年現在ES2019が発行されています。最新の機能についてはブラウザにより利用できる/できないが異なることがあります。

歴史的に見ると、ECMAScriptはJavaScriptから生まれたものであるものの、現在では、JavaScriptの実装規範となる「標準」としての存在になっています。

ちなみに、今は亡きActionScript3.0(Flash)は、ECMAScriptに準拠していました。

実行確認の方法

実行確認を行う場合はconsole.log()命令を使用して確認します。

例えば、変数の中身を確認したいときは以下のように書くことができます。

console.log(dogScaryVoice);

console.log()で出力する内容を確認するためにGoogle chromeを使用します。Google chromeを起動し、右クリックから「検証」を選んでください。インスペクタが起動し、ブラウザの右側(もしくは下側)に表示されます。インスペクタの下部に"Console"というタブがあり、その中にconsole.log()による出力が表示されます。

詳説は省きますが、変数の中身などを確認するときはこのような方法で確認すると思ってください。

変数宣言

ES2015からは変数を宣言するにはletを使用します。ブロックスコープのローカル変数を宣言することができます。

let dogScaryVoice = "wo..wo..woof!";

変数の宣言にはvarも使用することができますが、これはブロックスコープを持ちません。基本はletで宣言するようにしましょう。

if (true) {
  var x = 5;
}
console.log(x);  // x は 5
if (true) {
  let y = 5;
}
console.log(y);  // ReferenceError: y が定義されていない

定数宣言

ES2015からは定数を宣言するにはconstを使用します。
一度宣言した定数に代入しようとするとエラーになります。

const d = ["abc", "def", "ghi"];
d = ["xyz", "stu"]; // エラーが発生します!

ただし、定数はオブジェクトに設定した名札を変更できない(名札を別オブジェクトに貼り替えられない)という意味です。
参照するオブジェクトを変えなければ、定数として宣言したオブジェクトを変更することができます。

d[0] = "ABC"; // 定数でも要素の値は変えられる
d.push("xyz"); // メソッド呼び出しで追加もできる

データ構造とデータ型

ES2015では、新しい型が導入されているので、typeof演算子で自分が宣言した変数が何の型なのか調べられるようにしておきましょう。

let abc = "123";
typeof abc; // "string"

ES2015には、8つのデータ型が定義されており、プリミティブ型のデータ型が7つあります。

説明
Boolean true または false。
null null 値を意味する特殊なキーワードです。JavaScriptは大文字・小文字を区別するため、nullはNullやNULLなどとは異なります。
undefined 未定義。変数に何も設定しない場合、これを返します。
Number 整数または浮動小数点数。例えば42や3.14159など。0/0など数値として表現できないものはNaN(Not a Number)と判定されますが、これもNumber型の一種です。
BigInt 精度が自由な整数値。巨大な値を入れられる。例えば 9007199254740992nなど。
String テキストの値を表す連続した文字。"Howdy" など。
Symbol ES2015の新機能。インスタンスが固有で不変となるデータ型。一旦作ったシンボルは、それ自身とのみ等しくなり、ユニークなIDとして機能します。ES2015で導入された新機能の幾つかが、この仕組みに依存しています。
Object オブジェクト。クラスのインスタンス。

文字列(String)についての余談ですが、JavaScriptでは、プリミティブ値の文字列とStringオブジェクトの文字列は区別されます。必要に応じてプリミティブ値の文字列が自動的にStringオブジェクトに変換されるので、プリミティブ値の文字列に対してStringオブジェクトのメソッドを使用することができます。

let count = "123";
typeof count;
"string"
let count = new String("456");
typeof count;
"object"

データ型の変換(キャスト)についての余談ですが、JavaScriptは動的型付け言語です。そのため変数宣言時にデータ型を指定する必要がなく、またスクリプト実行時に必要に応じてデータ型が自動的に変換されます。

以下の数値と文字列の相互変換はプログラミング中に頻出するため、覚えておきましょう。

/**
 * 数値 ⇒ 文字列
 */

let abc = 123;

// 空の文字列("")と結合することで数値は文字列になります。
"" + abc; // "123"

// toString()メソッドを使用することで文字列に変換できます。
abc.toString(); // "123"

/*
 * 文字列 ⇒ 数値
*/

let str = "123";

// Number()メソッドを使用することで数値に変換できます。
Number(str); // 123

// parseInt()メソッドを使用することで数値に変換できます。
parseInt(str, 10); // 123

parseInt()を使用するときは、第2引数の10は必ず指定してください。これを指定することで、10進数で値を返すようになります。ES2015の仕様では10(10進法)がデフォルトですが、まだすべてのブラウザがサポートしている訳ではありません。したがって、parseInt()関数を使うとき基数(10)は必ず与えてください。

演算子

+,-,*,/,%については今までも使うことができましたが、ES2016から**が導入されました。これはa ** bと書くとaをb乗することができます。

3 ** 4 // 3 * 3 * 3 * 3と同じ意味

テンプレート文字列

ES2015からはテンプレート文字列が追加されました。従来であれば、文字列を結合するときに変数と文字列が混在することで'(クォーテーション)をたくさん書くことになってしまい、可読性が低下していました。テンプレート構文を使えば、クォーテーションを書く必要がなくなります。

テンプレート文字列はバッククォーテーションで文字列を囲みます。また、${ }の間に式を記述すると演算が行われ結果が文字列に埋め込まれます。

console.log(`こんにちは、${name}さん`);

比較演算子

ES2015では、特に比較演算子について書くことは無いのですが、プログラミングに不慣れな方がJavascriptを学習する際によくある誤解について紹介します。

Javascriptで比較を行うときは==は使わずに、===を使用してください。同様に!=は使わず、!==を使用しましょう。

===は厳密等価と呼ばれる比較方法です。===による評価では123 === "123"falseとなります。

その一方で==による評価では暗黙的な型変換が行われるため、123 === "123"trueになってしまいます。

スプレッド構文

ES2015からスプレッド構文を使用して既にある配列から別の配列を簡単に作成できるようになりました。スプレッド構文は...配列の変数のように書きます。

let arr = [123, 345, 567];

// [123, 345, 789]が展開され、arr2は[12, 34, 123, 345, 789]が設定される
let arr2 = [12, 34, ...arr];

配列(オブジェクト)をディープコピーする場合にも、以下のようにスプレッド構文を使えば簡単です。
ディープコピー・シャローコピーというキーワードにピンとこない方は「値渡し」や「参照渡し」で調べてみてください

let arr = [123, 456];
let arr2 = [...arr];

繰り返し処理(for...of文)

ES2015では配列要素の繰り返しを行う構文としてfor...of文が導入されました。for...ofは列挙可能なオブジェクト(配列、NodeList、argumentsなど)の操作に使用します。

const scores = [80, 72, 75, 60, 85];
let sum = 0;
// 繰り返しごとの値を設定するscoreはconstにすることができる
for (const score of scores) {
    // 添え字を使ったアクセスではなく、繰り返しごとの要素を使う
    sum += score;
}

「for...in文との違いは何だろう?」と思われた方もいらっしゃるかと思います。for...inとfor...ofの違いを一言で説明すると「処理対象がオブジェクトか値(配列、NodeList、argumentsなど)かの違い」になります。

オブジェクトを処理対象にするときはfor...in文、配列を対象にするときはfor...of文と覚えてください。

以下に検証を載せます。

var data = [ 'apple', 'orange', 'banana'];
//配列オブジェクトにhogeメソッドを追加
Array.prototype.hoge = function(){}
  for (var key in data){
   console.log(data[key]);
  }
// apple
// orange
// banana
// function(){}

配列にfor...in文を使用すると、function(){}という関係ないオブジェクトまで処理対象に含まれてしまっていることが分かります。

このようにならないようにfor...of文を使用します。

var data = [ 'apple', 'orange', 'banana'];
//配列オブジェクトにhogeメソッドを追加
Array.prototype.hoge = function(){}
  for (var value of data){
   console.log(value);
  }
// apple
// orange
// banana

for...of文を使用すると、正確に出力することができます。

関数のデフォルト引数

ES2015では関数内で使える構文として、デフォルト引数が導入されました。

function sample(a, b = 100) {
    console.log(a);
    console.log(b);
}
  
sample(1, 2); // aは1, bは2
sample(1); // aは1, bは100
sample(); // aはundefined, bは100

関数の残余引数

ES2015では関数内で使える構文として、残余引数が導入されました。

function sample(a, b, ...c) {
    console.log(a, b, c);
}
  
sample(1, 2); // aは1, bは2, cは[]
sample(1, 2, 3, 4); // aは1, bは2, cは[3, 4]

引数の展開

ES2015では関数に引数を渡すときにスプレッド構文を用いることで引数を展開することができます。

function sample(a, b) {
    console.log(a, b);
}
  
const ary = [1, 2, 3];
sample(ary); // aは[1, 2, 3], bはundefined
sample(...ary); // aは1, bは2 // aryが展開されてsample(1, 2, 3)という呼び出しになる。3つ目に対応する仮引数はないので無視される。

関数オブジェクト(関数式)

ES2015からの内容ではないのですが、関数オブジェクトの復習を兼ねてルールを書いてみます。関数は「宣言」と「リテラル」の2つの定義方法があります。

/*
 * 宣言
 */
function add(a, b) {
    return a + b;
}

/*
 * リテラル
 */
// リテラルとして関数を変数に代入する場合は関数名を省略して書くことができます。
const add = function(a, b) {
    return a + b;
};

// 関数名を省略しない書き方もできますが、これは冗長です。
const add = function add(a, b) {
    return a + b;
};
  • 「宣言」での定義
    functionで始める。
  • 「リテラル」での定義
    constもしくはletで始めて変数名(関数名)を書き、=に続いてfunction(引数) { 関数本体 };を書く。ちなみにこの書き方は関数名を省略して書くことができるので「無名関数」と呼ばれることがあります。

リテラル(関数式)を使う大きなメリットとして、「関数を受け取る関数」に、直接処理内容を渡すことができるということがあります。

function compute(func) {
    console.log(func(10, 20));
}

function add(a, b) {
    return a + b;
}

compute(add); // add関数をcompute関数に渡し、結果として30が返される

名前の巻き上げ

関数を文として定義した場合、関数の定義前に関数の呼び出しを行ってもコンパイルエラーにならず、期待した通りの実行結果になります。この効果を巻き上げ(hoisting)と呼びます。

console.log(add(1, 2)); // エラーにならず、計算結果として3を出力する。
function add(a, b) { return a + b; }

一方、式で定義した場合は「関数の定義が出てくる前に使う」ことはできません。これはデメリットではなく、見た目と一致する動作です。

console.log(add(1, 2)); // エラーになる
const add = function(a, b) { return a + b; };

アロー関数

アロー関数はES2015で導入された、関数式をより短く書ける方法です。
関数宣言のfunctionを省略し、=>で表現することができます。

const add = (a, b) => { return a + b; };
// const add = function(a, b) { return a + b; };

引数がひとつだけの場合は()を省略することができます。

const increment = a => { return a + 1; };
// const increment = function(a) { return a + 1; };

引数が無い場合には()を省略することはできません。

const showTest = () => { console.log("Test"); };
// const show = function() { console.log("Test"); };

return文だけの場合には{}returnを省略することができます。

const increment = a => a + 1;
// const increment = function(a) { return a + 1; };

関数の引数として関数を渡す場合にはアロー関数のメリットをより感じやすいと思います。

const compute = func => {
    console.log(func(10, 20));
};
  
// compute関数に渡す関数をアロー関数を用いて記述
compute((a, b) => a + b); // 30
compute((a, b) => a * b); // 200

できるだけアロー関数を使用したほうが良い理由

JavaScriptでは、thisという特殊なオブジェクトがあります。ES2015より前は、無名関数の中でthisを使用するときには工夫をする必要がありました。

例を挙げましょう。

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.say = function(){
    console.log("My name is " + this.name + ". I am " + this.age + " years old.");
};

Person.prototype.remember = function(){
    console.log("Hmm...");
    setTimeout(function(){
        // ここでsayを呼び出したいが、エラーになる
        this.say();
    }, 1000);
};

var person = new Person("Alice", 20);

person.remember();

remember()からsay()を呼び出そうとしていますが、これはエラーになります。
ES2015より前は呼び出すには以下の2つの方法がありました。

  • var self = this;と書き、self.say();で呼び出す。
Person.prototype.remember = function(){
    console.log("Hmm...");
    var self = this;
    setTimeout(function(){
        self.say();
    }, 1000);
};
  • .bind(this)と書いて、thisを紐づける。
Person.prototype.remember = function(){
    console.log("Hmm...");

    setTimeout(function(){
        this.say();
    }.bind(this), 1000);
};

ES2015では、匿名関数にアロー関数が使えますが、このアロー関数は親のthisオブジェクトがそのまま使えます。

class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    say(){
        console.log("My name is " + this.name + ". I am " + this.age + " years old.");
    }

    remember(){
        console.log("Hmm...");
        setTimeout(() => {
            this.say();
        }, 1000);
    }
}


let person = new Person("Alice", 20);
person.remember();

classという構文が出てきましたが、これについてはのちほど説明します。
いろいろと考えなくてよくなったのが有難いですね。

関数を受け取る関数

Javascriptには、map関数のように関数を引数として受け取る関数が数多く存在します。このような場合に渡される側の関数をコールバック関数と呼びます。このような場合にもアロー関数は便利です。

/*
 * map関数とアロー関数
 */
const arr = [1, 2, 3].map(num => num * 2); // arrは[2, 4, 6]になる

よく使う関数(メソッド)には以下のものがあります。

メソッド 内容
forEach(callback) 配列の要素一つ一つに関数で指定した何らかの処理をしてくれます。しかし、処理を行うだけなので以下のmapメソッドとは違い、undefinedを返します。
map(callback) 配列の要素に関数で指定した何かしらの処理を加えて新たな配列を作ってくれます。
filter(callback) コールバックの戻り値がtrueの要素のみの配列を返します。
find(callback) コールバックの戻り値が初めにtrueとなる最初の要素を返します。trueとなる要素がない場合はundefinedを返します。
reduce(callback, initialValue) 配列の各要素をreduceして単一の値を返します。

記述例

/*
 * forEachは配列の各要素に順次処理を行います。戻り値はありません。
 */
let ary1 = [1,2,3,4,5,6,7,8,9];
const forEachArray = [];
ary1.forEach((a) => {
    a += 1;
    forEachArray.push(a);
})
console.log(forEachArray);

/*
 * mapは配列の各要素を順次処理した結果を配列にまとめて返します。
 */
let ary2 = [1,2,3,4,5,6,7,8,9];
const mapArray = ary2.map((a) => {
    return a += 1;
})
console.log(mapArray);

/*
 * filterは配列の各要素のうち、戻り値としてtrueが返されたものを一つの配列にまとめて返します。
 */
let ary3 = [1,2,3,4,5,6,7,8,9];
const filterArray = ary3.filter((a) => {
    return (a % 2) === 0; //2の倍数のみ
})
console.log(filterArray);

/*
 * findは配列の各要素のうち、戻り値としてtrueが返されたものを返します。
 */
let ary4 = [1,2,3,4,5,6,7,8,9];
const findDigit = ary4.find((a) => {
    return a === 7;
})
console.log(findDigit);

/*
 * 最初は配列[1,2,3,4...]のうち、1,2がa,bに入る。その後は戻り値(2)がa、配列の次の要素(3)がbに入る。
 */
let ary5 = [1,2,3,4,5,6,7,8,9];
const reduceDigit = ary5.reduce((a, b) => {
    return a > b ? a : b;
})
console.log(reduceDigit); //今回は各要素の大きさを比較しているので、最終的に9がreduceDigitに代入される。

クロージャ

ES2015からの内容ではないのですが、「クロージャ」もあまり聞き慣れない言葉だと思うので解説します。

JavaScriptでは関数の中で関数を定義することができます。関数定義は宣言、リテラル、関数式、アロー関数どれを使用しても構いません。

例を挙げます。

const adder = () => {
    let sum = 0;
    const inner = (a) => {
        sum += a;
        return sum;
    }
    return inner;
}

let adder1 = adder();
console.log(adder1(3)); // 3
console.log(adder1(3)); // 6

let adder1 = adder();にて、外部関数adderが内部関数innerを返します。adder1(3)では内部関数innerが呼び出され、3が渡され、外部関数adderが持っているsum変数に加算します。1回目の呼び出しで3を渡すので、3が出力されます。2回目も3を渡していますが、前回の実行内容が引き継がれており、6が出力されています。

このコードが動作するということは直感的に理解できないかもしれません。いくつかのプログラミング言語では、関数内部のローカル変数はその関数が実行されている間だけ存在します。一旦adderの実行が完了したら、sum変数はもう必要とされなくなると考えた方が筋は通っています。ただこのコードが期待したとおりに動くという事は、これは明らかにJavaScriptにはあてはまりません。

この理由は、JavaScriptの関数はクロージャとなるためです。クロージャは関数とその関数が作られた環境という2つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆる変数によって構成されています。この場合、adder1はadderが実行された時に作られた inner関数のインスタンスへの参照です。inner関数のインスタンスはレキシカル(構文的な)環境への参照を保持し、そこにsum変数が存在します。このため、adderが実行された時にsum変数が残っていて 以前の値が引き継がれています。

オブジェクト

ES2015やES2018にてオブジェクトを作成する際にも新たな構文が導入されました。まずはオブジェクトの基本から確認しましょう。

他のプログラミング言語では「連想配列」や「辞書」や「ハッシュオブジェクト」などと呼ばれるものですが、Javascriptではこれを「オブジェクト」と呼びます。

まずはオブジェクトの作成方法です。オブジェクトは{}で囲み、「名前: 値」の組をコンマ区切りで記述します。これをオブジェクトリテラルと呼びます。配列と同様、最後の組の後にコンマを書いても無視されます。

const alice = {name: "Alice", age: 23, gender: "F"};

オブジェクトリテラルで指定した名前をプロパティ(property)と言います。プロパティにアクセスするには「オブジェクトの変数名.プロパティ名」と書きます。

console.log(alice.name);

プロパティのショートハンド

オブジェクトを作る際に、別途宣言している変数を使用してオブジェクトを作成することがあります。

// name, age, genderが事前に宣言されているとする
const person = {name: name, age: age, gender: gender};

この書き方は冗長なので、ES2015で「プロパティ名と、プロパティ値として参照する変数名が同じ」場合は以下のように書けるようになりました。

// name, age, genderが事前に宣言されているとする
const person = {name, age, gender};

スプレッド構文によるオブジェクトの作成

ES2018からスプレッド構文を使って既にあるオブジェクトから別のオブジェクトを作成することができるようになりました。まだ導入されたばかりの書き方のため、Microsoft Edgeなどの一部ブラウザでは文法エラーになります(2019年12月時点)。

const person2 = {...person};

ネスト

オブジェクトのプロパティには数値や文字列だけでなく、配列や別のオブジェクトを設定することができます。これにより、扱うデータを構造化することができます。

記述例

const alice = {
    name: {first: "Alice", last: "Carroll"},
    age: 23,
    gender: "F",
    pets: {dog:["Chappie", "Maron"], cat:["Mimi", "Pipi"], bird:["Pepe"]}
};

console.log(alice.name.first); // "Alice"
console.log(alice.pets.dog[1]); // "Maron"

JSON

JSONとは、JavaScript Object Notatonの略です。JavaScriptと付いてはいますがJavaScriptのオブジェクトを参考にして策定されたデータ交換フォーマットで、今では様々なプログラミング言語で利用できます。Webのクライアントとサーバの間で構造化されたデータをやり取りする際によく用いられます。

Javacriptの場合、JSONオブジェクトのstringifyメソッドを使うことで「JavaScriptオブジェクトを文字列」にできます。一方、JSONオブジェクトのparseメソッドを使うと「JSON表記の文字列をJavaScriptオブジェクト」に変換することができます。

// JavaScriptオブジェクトを文字列化
const jsonString = JSON.stringify(alice);

// 文字列(JSON表記)をJavaScriptオブジェクト化
const alice2 = JSON.parse(jsonString);

プロパティ名の計算

ES2015ではComputed property names(日本語に訳すと「計算されたプロパティ名」)という機能が追加されました。Computed propertyではプロパティ名を[]で囲みます。[]内には結果がプロパティ名になる式を記述します。

const name = "tel";
const value = "090-1234-5678";
const obj = {[name]: value};
console.log(obj); // {tel: "090-1234-5678"}

オブジェクトメソッド

オブジェクトにはメソッドを定義できます。自オブジェクト内の他のプロパティやメソッドを呼び出したいときはthis.プロパティ名this.メソッド名と記述します。

const alice = {
    name: "Alice",
    age: 23,
    gender: "F",
    hello: function() {
        console.log(`Hello! My name is ${this.name}.`);
    },
    callHello: function() {
        this.hello();
    }
};

alice.hello(); // "Hello! My name is Alice."
alice.callHello(); // "Hello! My name is Alice."

オブジェクトメソッドのショートハンド

ES2015ではShorthand method namesが導入されました。これを使うとhelloメソッドの定義を以下のように少し短く書くことができます。

const alice = {
    name: "Alice",
    age: 23,
    gender: "F",
    hello() {
        console.log(`Hello! My name is ${this.name}.`);
    }
};

alice.hello(); // "Hello! My name is Alice."

thisを束縛する

先の項で「できるだけアロー関数を使用したほうが良い理由」として、「アロー関数でメソッドを作成すると親のオブジェクトのthisを使用できる」と解説しました。これは言い換えれば、「thisを自オブジェクトに束縛しない」ということになります。

以下の例では、アロー関数はthisを自オブジェクトに束縛しないため、nameプロパティを参照することができず、実行結果は「Hello! My name is 」になります。

const alice = {
    name: "Alice",
    age: 23,
    gender: "F",
    hello: () => {
        console.log(`Hello! My name is ${this.name}.`); // Hello! My name is 
    }
};

thisを自オブジェクトに束縛したいのであれば、アロー関数は使わずにfunction(){}で宣言してください。

オブジェクトの中にメソッドを定義する場合には、以下のようにオブジェクトの中で即時関数を使いたい場合など、「thisを今の文脈のまま使いたい」「thisを束縛したくない」というケースのみアロー関数を使いましょう。

const alice = {
    name: "Alice",
    age: 23,
    gender: "F",
    parrent: "Mike",
    sayParrent: function() {
        return () => {
            console.log(`My parrent is ${this.parrent}.`);
        }
    }
};

console.log(`Hi ${alice.name}, What your parrent name ?`); // "Hi Alice, What your parrent name ?"
let say = alice.sayParrent();
say(); // "My parrent is Mike."

クラス

ES2015からクラス機能が導入されました。ですが、それ以前にもクラスを実現する方法はありました。
JavaScriptは「プロトタイプベース」と呼ばれるオブジェクト指向言語です。プロトタイプベースのオブジェクト指向言語では「あるオブジェクトをテンプレートとして新しいオブジェクトを作る」ということが行われます。この仕組みはES2015以降でも変わりません。
実はクラス機能は以下で説明する従来の書き方をよりわかりやすく書けるようにしたものです。

  • プロトタイプでのオブジェクト作成(ES2015より前の書き方)
// classではなくfunction(つまり関数)としてコンストラクタを定義
function Person(name, age, gender) {
    this.name   = name;
    this.age    = age;
    this.gender = gender;
}

/**
 * 関数オブジェクトにはprototypeというプロパティ(オブジェクト)がある。
 * このオブジェクトに関数を設定することでメソッドが定義できる。
 */
Person.prototype.hello = function() {
    console.log(`Hello! My name is ${this.name}.`);
}

// オブジェクトを作成(new)する方法は昔も今も変わらない
const alice = new Person("Alice", 23, "F");

続いて、クラスでのオブジェクトの作成方法です。ES2015では「オブジェクトの型(種類)」を定義するクラス(class)機能が導入されました。クラス機能を使ってPersonを定義すると以下のようになります。なおPersonはクラス名と呼ばれ、他の変数と区別するために通常先頭を大文字にします。

  • クラスでのオブジェクト作成
class Person {
    // コンストラクタ
    constructor(name, age, gender) {
        this.name   = name;
        this.age    = age;
        this.gender = gender;
    }

    // メソッド
    hello() {
        console.log(`Hello! My name is ${this.name}.`);
    }
}

/**
 * クラスからオブジェクトを作成するにはnew演算子を使用します。
 * new演算子は空のオブジェクト({ })を作成し、作成したオブジェクトをthisとして
 * 指定クラスのコンストラクタを実行、初期化されたオブジェクトを結果として返すという
 * 動作を行います。
 */
const alice = new Person("Alice", 23, "F");
const bob   = new Person("Bob",   32, "M");

クラスメソッド

クラスメソッド(静的メソッド)を定義するにはメソッドの前にstaticキーワードを付けます。

class Person {
    constructor(name, age, gender) {
        this.name   = name;
        this.age    = age;
        this.gender = gender;
    }
  
    // インスタンスメソッド
    hello() {
        console.log(`Hello! My name is ${this.name}.`);
    }
  
    // クラスメソッド
    static greeting() {
        console.log("Hi, this message is shown using class method.")
    }
}
  
// クラスメソッド呼び出し
Person.greeting();
  
// インスタンスからクラスメソッドは呼べない
const alice = new Person("Alice", 23, "F"); // Hi, this message is shown using class method.
alice.greeting(); // エラーになる!

// constructor経由で呼ぶことは可能(あまり使いません)
alice.constructor.greeting(); // Hi, this message is shown using class method.

インスタンスからクラスメソッドを呼ぶことはできません。つまり、aliceがPersonインスタンスだとして、alice.greeting();と書くと実行時エラーになります。ただし、裏のからくりを知っていれば呼び出すことは可能です。JavaScriptの「クラス」の正体はコンストラクタ関数(constructor(){})です。結局のところ、クラスメソッド(静的メソッド)とは「コンストラクタ関数オブジェクトのプロパティとして設定された関数オブジェクト」ということになります。

継承

クラスは「継承」という仕組みを使うことで拡張(機能追加)することができます。
Javascriptでは、オーバーライド(親クラスと同じ名前のメソッドを定義する)は使用することができますが、オーバーロード(引数の数を変えて同じ名前のメソッドをいくつも定義する)を使用することができません。メソッドはそもそも「プロパティ名に対して設定された関数オブジェクト」であり、「引数は何個でも受け取れるし無視してもいい」というものなので言語としてオーバーロードを扱うことができません。

class Person {
    constructor(name, age, gender) {
        this.name   = name;
        this.age    = age;
        this.gender = gender;
    }

    hello() {
        console.log(`Hello! My name is ${this.name}.`);
    }

    sayAge() {
        console.log(`I am ${this.age} years old.`);
    }
}

class Teacher extends Person {
    constructor(name, age, gender, subject) {
        // スーパークラスの呼び出し
        super(name, age, gender);
        // サブクラスのプロパティを設定
        this.subject = subject;
    }

    // メソッドのオーバーライド
    hello() {
        // スーパークラスのメソッドを呼び出し
        super.hello();
        console.log(`I teach ${this.subject}.`);
    }

    // サブクラスの追加メソッド
    goodbye() {
        console.log("Good bye.");
    }
}
  
// aliceをTeacherインスタンスとして作成
const alice = new Teacher("Alice", 23, "F", "English");
alice.hello(); // "Hello! My name is Alice. I teach English."

// TeacherはPersonを経由しているのでPersonで定義されているメソッドを呼び出せる
alice.sayAge(); // "I am 23 years old."

// サブクラスで定義したメソッドの呼び出し
alice.goodbye(); // "Good bye."

オブジェクトの種類

  • ビルトインオブジェクト
    • ECMAScriptにおいて、JavaScriptの標準仕様として規定されているオブジェクト。

ビルトインオブジェクトには以下があります。

オブジェクト名 説明
Array 配列を作成・操作
Boolean 真偽値を作成・操作
Date 日付や時刻の取得・設定
Function 関数の作成・操作
Math 数値計算関連
Number 属性を持つ数値を作成
Object ユーザ定義オブジェクトを作成
RegExp 正規表現関連
  • Webブラウザーのオブジェクト
    • Webブラウザーが独自に備えているオブジェクト。ブラウザーによって提供される。
    • 画像の中に「document」というオブジェクトがあり、ここからHTMLを操作することができます。HTMLをオブジェクトとして扱うのでDOM(Document Object Model)と呼ばれています。

window_object_composition

  • 開発者が独自に作成するオブジェクト
    • ユーザー自身が独自に作成するオブジェクト。

Webブラウザーオブジェクトとビルトインオブジェクトは各ブラウザーが独自に用意するオブジェクトですが、ほとんどの機能が共通化されているので「JavaScriptの標準機能」と考えてよいでしょう。

余談ですが、今まで使用してきたブラウザのコンソールに出力させるための命令は、console.log("");でしたが、正確にはwindow.console.log("");となります。windowオブジェクトは特別な扱いを受けており、windowオブジェクトのプロパティは「window.」を省略してアクセスすることができます。

エクスポート/インポート

ES2015からオブジェクトをエクスポート/インポートする機能が追加されました。
JavaScriptファイル内でexport文を書くことによりエクスポート(別のモジュールから使うことができる機能の公開)が行えます。

  • エクスポート
    エクスポートは通常の変数宣言や関数宣言などの前にexportを付けます。
    後からまとめてエクスポートすることもできます。モジュール(ファイル)が長くなる場合は最後にまとめてエクスポートする形式の方がプログラムを読み解く人にはわかりやすいでしょう。

  • デフォルトエクスポート
    デフォルトエクスポートは変数宣言や関数宣言などの前にexport defaultを付けます。1モジュールに一つだけ指定できます。
    こちらも定義とエクスポートを分けて書くこともできます。
    なお、モジュールの設計としては「1モジュールにつき1機能だけをエクスポート(つまりデフォルトエクスポートを使う)」のがよいと言われています。

/**
 * エクスポート
 */
export const varA = 123;
export function funcB() {
    console.log("funcB");
};


// 変数、関数を定義しておいて後でまとめてエクスポートすることも可能です。
// asを付けるとエクスポートする名前を変えることも可能です。
const varA = 123;
function funcB() {
    console.log("funcB");
};
export {varA as varC, funcB};


/**
 * デフォルトエクスポート
 */
export default function sample1() {
    console.log("sample1")
};

// 変数、関数を定義しておいて後でデフォルトエクスポートすることも可能
function sample2() {
    console.log("sample2")
};
export default sample2;

// 名前付きエクスポートで「as default」と書くことでもデフォルトエクスポートが行える
function sample3() {
    console.log("sample3")
};
export {sample3 as default};

オブジェクトのエクスポートができたら、続いてオブジェクトのインポートを行ってみましょう。
エクスポートされたオブジェクトを取り込むにはimport文を使用します。インポートの方法は次の三種類があります。

  • 名前付きでエクスポートされたオブジェクトの取り込み
  • エクスポートされているオブジェクトの全取り込み
  • デフォルトエクスポートの取り込み
/**
 * インポート
 */

// 名前付きエクスポートされているオブジェクトを取り込み(ClassCは取り込まない)
import {varA as varC, funcB} from "./sample09_2_1";

// エクスポートされているオブジェクトを全部取り込む
import * as sample09_2_2 from "./sample09_2_2";
// 全取り込みの場合はピリオド付きでアクセスする必要がある
console.log(sample09_2_2.varA);

// デフォルトエクスポートされているオブジェクトを取り込む
import sample09_2_3 from "./sample09_2_3";

アグリゲート(Aggregate/集める)

モジュールを作成する際、規模が大きくなってくると「モジュールをさらに細かな単位(サブモジュール)に分ける」ということを行います。JavaScriptではサブモジュールごとにファイルを作成することになります。
インポートの際に「各サブモジュールを個別に取り込む」と煩雑になります。サブモジュールごとに取り込むのではなく、「モジュールの代表ファイルから一度に取り込む」ことができると便利です。このような「モジュールの代表ファイル(サブモジュールのエクスポートを全部まとめたファイル)」を作成することをモジュールのアグリゲート(aggregate/集める)と言います。

アグリゲートを行うにはexport文の最後にfromを付けます。動作としては「指定モジュールからオブジェクトを取り込み(import)」「それを改めてexport」という処理を一度に行うということになります。

// sample1モジュールでエクスポートされている全オブジェクトを再エクスポート
export * from "./sample1";
// sample2モジュールでエクスポートされているオブジェクトのうちvar2だけ再エクスポート
export {var2} from "./sample2";
// sample3モジュールのデフォルトエクスポートをvar3という名前で再エクスポート
export {default as var3} from "./sample3";

非同期処理

ES2015から導入されたPromiseとPromise.all、そしてES2017から導入されたasyncとawaitについては、記事が長くなりそうなので別記事でまとめます。

参考資料
歴史から学ぶECMAScriptとJavaScriptの違い
文法とデータ型 - JavaScript | MDN
配列にfor...inはダメと言うけれど、 - Qiita
JavaScriptのbind(this)とself=thisはどちらを使うべきか
クロージャ - JavaScript | MDN
ビルトインオブジェクト - Qiita
JavaScriptに欠かせない「ビルトインオブジェクト」の基礎知識 (1/2):JavaScript標準ライブラリの使い方超入門(1) - @IT
JavaScript 〜ブラウザオブジェクトを操作してみよう〜 - Qiita

SmokyDog

I love dog and web.

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.