ECMAScript 2015

JavaScript クラスで始める、

オブジェクト指向プログラミング入門

index

  • オブジェクト指向プログラミング
  • ES5の(擬似的な)クラス
  • ES2015のクラス
  • 関数のみとクラスの比較
  • まとめ

オブジェクト指向プログラミング

オブジェクト指向プログラミングとは?

オブジェクト指向プログラミング(オブジェクトしこうプログラミング、英: object-oriented programming, OOP)は、コンピュータ・プログラミングのパラダイムのひとつで、オブジェクト指向の概念や手法を取り入れたものである。プログラムを、データとその振舞が結び付けられたオブジェクトの集まりとして構成する、などといった特徴がある。このパラダイムを指向しているプログラミング言語がオブジェクト指向プログラミング言語である。

ざっくりいうと、

「オブジェクトを利用し、再利用性を高めたプログラミング」

オブジェクト指向プログラミングの方式

  • クラスベース方式(継承ベース)

  • プロトタイプベース方式(インスタンスベース)

  • mixin方式

JavaScriptはプロトタイプベース方式のオブジェクト指向プログラム

クラス

クラスとは?

オブジェクトを利用し、データや操作を隠蔽(カプセル化)して、再利用性を高めるプログラミングの手法

用語解説

コンストラクタ関数 オブジェクトの設計図の役割を果たす関数(JavaScriptにおける、クラスの本体)
インスタンス コンストラクタ関数(クラス)から生成したオブジェクト
メンバ変数 インスタンスが保持するデータ
インスタンスメソッド インスタンスが保持する関数(メンバ関数)
プロトタイプメソッド コンストラクタ関数(クラス)のprototypeプロパティに追加された関数

注意:JavaScriptがクラスベース方式を採用したわけでない

ECMAScript 2015 で導入された JavaScript クラスは、JavaScript にすでにあるプロトタイプベース継承の糖衣構文です。クラス構文は、新しいオブジェクト指向継承モデルを JavaScript に導入しているわけではありません。

MDN:クラスより抜粋

糖衣構文(とういこうぶん、英: syntactic sugar)は、プログラミング言語において、読み書きのしやすさのために導入される書き方であり、複雑でわかりにくい書き方と全く同じ意味になるものを、よりシンプルでわかりやすい書き方で書くことができるもののことである。 by wikipedia

ES5の(擬似的な)クラス

インスタンスメソッド

// コンストラクタ関数
function Foo(fuga) {
  // var this = {}; // new はこう解釈する
  this._fuga = fuga;
  this.method = function() {
    console.log(this._fuga);
  };
  // return this; // new はこう解釈する
};

// インスタンス化
var foo = new Foo('foo');

// fooオブジェクトのインスタンスメソッド実行
foo.method(); // -> foo

// foo2オブジェクトのインスタンスを作り、インスタンスメソッドを実行
var foo2 = new Foo('foo2');
foo2.method(); // -> foo2

// foo.method と foo2.methodは、異なるオブジェクトのインスタンスメソッド
console.log(foo.method === foo2.method); // -> false

プロトタイプメソッド

// コンストラクタ関数
function Foo(fuga) {
  // var this = {}; // new はこう解釈する
  this._fuga = fuga;
  // return this; // new はこう解釈する
};

// プロトタイプに追加
// 関数を作った時点で、prototypeプロパティが生成される
Foo.prototype.method = function() {
  console.log(this._fuga);
};

// インスタンス化
var foo = new Foo('foo');

// fooオブジェクトのプロトタイプメソッドを実行
foo.method(); // -> foo

// foo2オブジェクトを作成し、のプロトタイプメソッドを実行
var foo2 = new Foo('foo2');
foo2.method(); // -> foo2

// 同じメソッド
console.log(foo.method === foo2.method); // -> true

比較

// コンストラクタ関数
function Foo(fuga) {
  // var this = {}; // new はこう解釈する
  this._fuga = fuga;
  // return this; // new はこう解釈する
};

// プロトタイプに追加
// 関数を作った時点で、prototypeプロパティが生成される
Foo.prototype.method = function() {
  console.log(this._fuga);
};

// インスタンス化
var foo = new Foo('foo');

// fooオブジェクトのプロトタイプメソッドを実行
foo.method(); // -> foo

// foo2オブジェクトを作成し、のプロトタイプメソッドを実行
var foo2 = new Foo('foo2');
foo2.method(); // -> foo2

// 同じメソッド
console.log(foo.method === foo2.method); // -> true
// コンストラクタ関数
function Foo(fuga) {
  // var this = {}; // new はこう解釈する
  this._fuga = fuga;
  this.method = function() {
    console.log(this._fuga);
  };
  // return this; // new はこう解釈する
};

// インスタンス化
var foo = new Foo('foo');

// fooオブジェクトのインスタンスメソッド実行
foo.method(); // -> foo

// foo2オブジェクトのインスタンスを作り、インスタンスメソッドを実行
var foo2 = new Foo('foo2');
foo2.method(); // -> foo2

// foo.method と foo2.methodは、異なるオブジェクトのインスタンスメソッド
console.log(foo.method === foo2.method); // -> false

インスタンスメソッド

プロトタイプメソッド

特別な理由がない限りは、プロトタイプメソッドのパターンがおすすめ

ECMAScript 2015のクラス

Classの基本構文

// クラス宣言
class Person {
  // コンストラクタ関数
  constructor(firstName) {
    this._lastName = 'Otsuka';
    this._firstName = firstName;

    // インスタンスメソッド
    this.insMethod = () => {
      console.log('[instance]', `私は、${this._firstName} ${this._lastName}です。`);
    }
  }

  // プロトタイプメソッド
  method() {
    console.log('[prototype]', `私は、${this._firstName} ${this._lastName}です。`);
  }
}

// クラス式
// 宣言でも式でも、巻き上がりは起こらない。
// const Person = class {};
// or
// const Person = class Person {};

// クラスは関数。やっていることは、ES5の頃と同じ。
console.log(typeof Person); // -> function

// インスタンス化
const person = new Person('Yuhi');

// インスタンス化することにより、Object になる
console.log(typeof person); // -> object

// プロトタイプメソッドの実行
person.method();  // -> [prototype] 私は、Yuhi Otsukaです。

// インスタンスメソッドの実行(インスタンス毎に作成される)
person.insMethod();  // -> [prototype] 私は、Yuhi Otsukaです。

静的メソッド

// クラス宣言
class Person {
  // コンストラクタ関数
  constructor(firstName) {
    this._lastName = 'Otsuka';
    this._firstName = firstName;
  }

  // プロトタイプメソッド
  method() {
    console.log('[prototype]', `私は、${this._firstName} ${this._lastName}です。`);
  }

  // 静的メソッド
  static staMethod() {
    console.log('[static]');
  }
}

// インスタンス化するまえでも、実行できる
Person.staMethod(); // -> [static]

// これは駄目
Person.method(); // -> Uncaught ReferenceError: person is not defined

getterとsetter

// クラス宣言
class Person {
  // コンストラクタ関数
  constructor(firstName) {
    this._lastName = 'Otsuka';
    this._firstName = firstName;
  }

  // getter
  get changeLastName() {
    return `婿入りして、${this._firstName} ${this._lastName}になりました。`;
  }

  // setter
  set changeLastName(val) {
    this._lastName = val;
  }

  // プロトタイプメソッド
  method() {
    console.log('[prototype]', `私は、${this._firstName} ${this._lastName}です。`);
  }
}

// インスタンス化
const person = new Person('Yuhi');

// メソッドの実行
person.method();
// -> [prototype] 私はYuhi Otsukaです。

// setter
// _lastNameプロパティを上書き
person.changeLastName = 'Kato';

// プライベート変数の上書きはご法度!
// person._lastName = 'Kato';

// getter
// changeLastNameプロパティに文字列を追加
console.log('[getter]', person.changeLastName);
// -> [getter] 婿入りして、Yuhi Katoになりました。

継承(サブクラス)

// クラス宣言
class Person {
  // コンストラクタ関数
  constructor(firstName) {
    this._lastName = 'Otsuka';
    this._firstName = firstName;
  }

  // getter
  get changeLastName() {
    return `婿入りして、${this._firstName} ${this._lastName}になりました。`;
  }

  // setter
  set changeLastName(val) {
    this._lastName = val;
  }

  // プロトタイプメソッド
  method() {
    console.log('[prototype]', `私は、${this._firstName} ${this._lastName}です。`);
  }
}

// 継承
class Child extends Person {
  childMethod() {
    // 親のコンストラクタを継承
    console.log('[extends]', `私の名前は、${this._firstName}です。`);

    // 親メソッドの呼び出し
    super.method();
  }
}

// 親クラスのコンストラクタ関数を継承し、new してインスタンス化
const child = new Child('Takeshi');

// プロトタイプメソッドの実行
child.childMethod();
// -> [extends] 私の名前は、Otsukaです。
// -> [prototype] 私は、Takeshi Otsukaです。

// 親クラスの get と set を実行
child.changeLastName = 'Kato';

// extends getter
console.log('[extends][getter]', child.changeLastName);
// -> [extends][getter] 婿入りして、Takeshi Katoになりました。

関数のみとクラスの比較

this名前を知りたいプログラム

関数のみ

const funcPerson = (firstName, lastName) => {

  // それぞれの関数を定義
  const sayThis = () => {
    console.log(this);
  };

  const sayFirstName = () => {
    console.log(firstName);
  };

  const sayLastName = () => {
    console.log(lastName);
  };

  const sayFullName = () => {
    console.log(firstName, lastName);
  };

  // 必要なものを実行
  sayThis();
  sayFirstName();
};

// thisを知りたい and ファーストネームを聞きたい
funcPerson('Yuhi', 'Otsuka');
// -> window{}
//    Yuhi

funcPerson('Takeshi', 'Kato');
// -> window{}
//    Takesi

// ラストネームを聞きたい
// -> funcPerson関数の sayFirstName(); を sayLastName(); に書き換えて、
//    sayThis()をコメントアウトして、実行

// フルネームを聞きたい
// -> funcPerson関数の sayFirstName(); を sayFullName(); に書き換えて、
//    sayThis()をコメントアウトして、実行

クラス

class Person {
  constructor(firstName, lastName) {
    this._firstName = firstName;
    this._lastName = lastName;
  }

  // それぞれのメソッドを設定
  sayThis() {
    console.log(this);
  }

  sayFirstName() {
    console.log(this._firstName);
  }

  sayLastName() {
    console.log(this._lastName);
  }

  sayFullName() {
    console.log(this._firstName, this._lastName);
  }
}

const person = new Person('Yuhi', 'Otsuka');
const person2 = new Person('Takeshi', 'Kato');

person.sayThis(); // -> Person{}

// ファーストネームを聞きたい
person.sayFirstName(); // -> Yuhi
person2.sayFirstName(); // -> Takeshi

// ラストネームを聞きたい
person.sayLastName(); // -> Otsuka
person2.sayLastName(); // -> Kato

// フルネームを聞きたい
person.sayFullName(); // -> Yuhi Otsuka
person2.sayFullName(); // -> Takeshi Kato

まとめ

まとめ

  • オブジェクト指向プログラミングとは、オブジェクトを利用し、再利用性を高めたプログラミング
  • JavaScriptはプロトタイプベース方式のプログラミング言語
  • ES5では擬似的にクラスを表現していた
  • ES2015からClassが使えるようになった
  • クラスを使うと、再利用性の高いコードが書きやすい

ECMAScript 2015 JavaScript クラスで始める、オブジェクト指向プログラミング入門

By Otsuka Yuhi

ECMAScript 2015 JavaScript クラスで始める、オブジェクト指向プログラミング入門

  • 581