このコンテンツはまだ翻訳されていません。 翻訳に協力してください

Class パターン

オブジェクト指向プログラミングでは、クラス はオブジェクト生成、状態(メンバ変数)の初期値の提供や振る舞いの実装(メンバ関数またはメソッド)のための拡張可能なプログラムコードテンプレートです。

Wikipedia

JavaScriptには特別な構文構文とキーワード class があります。しかしそれを学ぶ前に、 “クラス” という言葉はオブジェクト指向プログラミングの理論に由来すると考えるべきでしょう。定義は上記で引用されている通り、言語に依存しません。。

JavaScriptでは、class キーワードを使わずにクラスを作成する、いくつかのよく知られたプログラミングパターンがあります。そしてここでは最初にそれについて話しましょう。

class の構造は次のチャプターで説明しますが、JavaScriptでは、それは “シンタックスシュガー” であり、ここで学ぶパターンの1つの拡張です。

関数クラスパターン

下のコンストラクタ関数は定義に従って、“クラス” と考えることができます。:

function User(name) {
  this.sayHi = function() {
    alert(name);
  };
}

let user = new User("John");
user.sayHi(); // John

それは定義のすべての部分に従います:

  1. オブジェクトを作成する (new で呼び出し可能) ための “プログラムコードテンプレート” である。
  2. 状態 (パラメータから name) の初期値を提供する。
  3. メソッド (sayHi) を提供する。

これは、関数的なクラスパターン(functional class pattern) と呼ばれます。

関数的なクラスパターンでは、this に割り当てられていない User の中のローカル変数とネストされた関数は内側からは見えますが、外のコードからはアクセスできません。

なので、私たちは簡単に内部関数や変数を追加することができます。次の calcAge() のように:

function User(name, birthday) {

  // User 内の他のメソッドからのみ見えます
  function calcAge() {
    return new Date().getFullYear() - birthday.getFullYear();
  }

  this.sayHi = function() {
    alert(name + ', age:' + calcAge());
  };
}

let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John

このコードでは、変数 name, birthday と関数 calcAge() はオブジェクトに対しては内部、private です。それはその内側からのみ見えます。

一方、sayHi は外部、public メソッドです。user を作成する外部コードはそれにアクセスできます。

このようにして、内部実装の詳細とヘルパーメソッドを外部コードから隠すことができます。 thisに割り当てられたものだけが外側に見えるようになります。

ファクトリークラスパターン

私たちは、new を全く使わずにクラスを生成することができます。

このようになります:

function User(name, birthday) {
  // User 内の他のメソッドからのみ見えます
  function calcAge() {
    return new Date().getFullYear() - birthday.getFullYear();
  }

  return {
    sayHi() {
      alert(name + ', age:' + calcAge());
    }
  };
}

let user = User("John", new Date(2000,0,1));
user.sayHi(); // John

ご覧の通り、関数 User はパブリックなプロパティとメソッドを持つオブジェクトを返します。このメソッドの唯一のメリットは、new を省略できることです。: let user = new User(...) の代わりに let user = User(...) と書きます。別の側面では、ほとんど関数的なパターンと同じです。

プロトタイプベースのクラス

プロトタイプベースのクラスは最も重要で一般的にベストです。 実際には、関数的なクラスパターンとファクトリクラスパターンはほとんど使用されません。

後ほどなぜかをお見せします。

ここでは、プロトタイプを使って同じクラスを再度書いています。:

function User(name, birthday) {
  this._name = name;
  this._birthday = birthday;
}

User.prototype._calcAge = function() {
  return new Date().getFullYear() - this._birthday.getFullYear();
};

User.prototype.sayHi = function() {
  alert(this._name + ', age:' + this._calcAge());
};

let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John

コード構造:

  • コンストラクタ User は現在のオブジェクトの状態のみを初期化します。
  • メソッドは User.prototype に追加されています。

ご覧の通り、メソッドは字句的に function User の中にはなく、共通のレキシカル環境を共有しません。もし function User の内側で変数を宣言した場合、それらはメソッドには見えません。

従って、内部プロパティとメソッドはアンダースコア "_" が先頭に追加されているという、広く知られている合意があります。_name または _calcAge() のように。技術的にはそれは単なる合意であり、外部コードは依然としてそれらにアクセスすることはできます。しかし、ほとんどの開発者は "_" の意味を理解しており、外部コードの中でプロフィックスのついたプロパティやメソッドを触らないようにしています。

関数的なパターンと比較した時の利点は次の通りです。:

  • 関数的なパターンでは、各オブジェクトにはすべてのメソッドの独自のコピーがあります。 コンストラクタで this.sayHi = function(){...} と他のメソッドの別のコピーを割り当てます。
  • プロトタイプ的なパターンでは、すべてのメソッドはすべての user オブジェクトで共有される User.prototype にあります。オブジェクト自身はデータだけを保持しています。

従って、プロトタイプパターンはよりメモリ効率が良いです。

…しかし、それだけではありません。プロトタイプを使うと継承を効率的にセットアップすることができます。すべての組み込みのJavaScriptオブジェクトはプロトタイプを使っています。また特別な構文構造があります: “class” は、見た目の良い構文を提供します。 そしてもっと多くのことがありますので、それらと一緒に進んでみましょう。

クラスのためのプロトタイプベースの継承

2つのプロトタイプベースのクラスを持っているとしましょう。

Rabbit:

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(this.name + ' jumps!');
};

let rabbit = new Rabbit("My rabbit");

…そして Animal:

function Animal(name) {
  this.name = name;
}

Animal.prototype.eat = function() {
  alert(this.name + ' eats.');
};

let animal = new Animal("My animal");

今はそれらは完全に独立しています。

しかし、私たちは RabbitAnimal を拡張させたものにしたいです。言い換えると、rabbits は animals をベースとし、Animal のメソッドへのアクセスを持ち、自身のメソッドでそれを拡張する必要があります。

プロトタイプの言語ではどういう意味でしょうか?

今、rabbit オブジェクトのメソッドは Rabbit.prototype にあります。Rabbit.prototype にメソッドが見つからない場合, rabbit に “フォールバック” として Animal.prototype を使ってほしいです。

なので、プロトタイプチェーンは rabbitRabbit.prototypeAnimal.prototype である必要があります。

このように:

それを実装するコードは次の通りです:

// 以前と同じ Animal
function Animal(name) {
  this.name = name;
}

// すべての animals 食べることができますよね?
Animal.prototype.eat = function() {
  alert(this.name + ' eats.');
};

// 以前と同じ Rabbit
function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(this.name + ' jumps!');
};

// 継承チェーンを設定します
Rabbit.prototype.__proto__ = Animal.prototype; // (*)

let rabbit = new Rabbit("White Rabbit");
rabbit.eat(); // rabbits も食べることができる
rabbit.jump();

{*} はプロトタイプチェーンを設定します。つまり、rabbit はまず Rabbit.prototype のメソッドを検索し、次に Animal.prototype を検索します。そして、完全性のために、もしメソッドが Animal.prototype に存在しない場合、Object.prototype で検索を継続します。Animal.prototype は通常のオブジェクトなので、それを継承しています。

ここはその完全なイメージです。:

サマリ

“クラス” の言葉はオブジェクト指向プログラミングからきています。JavaScriptでは、それは通常関数クラスパターンまたはプロトタイプパターンを意味します。プロトタイプパターンはより協力でメモリ効率がよいので、それを使うことを推奨します。

プロトタイプのパターンによれば:

  1. メソッドは Class.prototype に格納されます。
  2. プロトタイプはお互いから継承します。

次のチャプターでは、class キーワードと構造について学びます。プロトタイプクラスを短く書くことができ、いくつかの追加の利点があります。

タスク

重要性: 5

下のプロトタイプ継承でのエラーを見つけてください。

何が間違っていますか?どのような結果になるでしょうか?

function Animal(name) {
  this.name = name;
}

Animal.prototype.walk = function() {
  alert(this.name + ' walks');
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = Animal.prototype;

Rabbit.prototype.walk = function() {
  alert(this.name + " bounces!");
};

エラーとなる行はここです:

Rabbit.prototype = Animal.prototype;

ここで、Rabbit.prototypeAnimal.prototype は同じオブジェクトになります。なので、両方のクラスのメソッドがそのオブジェクトに混在します。

結果として、Rabbit.prototyp.walkAnimal.prototype.walk を上書きするので、すべての animals は跳ね始めます(bounce)。:

function Animal(name) {
  this.name = name;
}

Animal.prototype.walk = function() {
  alert(this.name + ' walks');
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = Animal.prototype;

Rabbit.prototype.walk = function() {
  alert(this.name + " bounces!");
};

let animal = new Animal("pig");
animal.walk(); // pig bounces!

正しいバリアントは次の通りです:

Rabbit.prototype.__proto__ = Animal.prototype;
// もしくはこのような形です:
Rabbit.prototype = Object.create(Animal.prototype);

これによりプロトタイプは分離され、それぞれ対応するクラスのメソッドを格納します。が、Rabbit.prototypeAnimal.prototype を継承します。

重要性: 5

Clock クラスは関数スタイルで書かれています。プロトタイプを使って書き直してください。

P.S. 時計はコンソールで動きます、開いて見てください。

タスクのためのサンドボックスを開く

関数型での内部のプロパティ(template, timer) と内部メソッド render はアンダースコア _ でプライベートとしてマークされていることに注意してください。

サンドボックスで解答を開く

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。