Javascriptでオブジェクト指向するときに覚えておくべきこと

More than 5 years have passed since last update.

Javascriptはブラウザのクライアントサイドで動く唯一の言語と言ってもいいので、普段書かなくてもちょいちょい書くことになる。そんな時用に、他の言語使っていると忘れてしまうJavascriptの重要な法則をまとめておく。

基本的にリファレンスにしているのはMozilla Developer Network (MDN)のドキュメントの以下のページ。MDNはJavascript関連では一番ちゃんとしたドキュメントだと信じている。


プロトタイプベース言語

Javascriptはプロトタイプベースのオブジェクト指向言語で、クラスベースのオブジェクト指向言語(例: C++, Java)とは異なる部分が多々ある。

例えば、クラスベース言語はクラスとインスタンスという概念があるのに対し、プロトタイプベース言語はPrototypical Objectというオブジェクトがあるだけ。


表8.1 クラスベース (Java) のオブジェクトシステムとプロトタイプベース (JavaScript) のオブジェクトシステムとの比較

クラスベース (Java)
プロトタイプベース (JavaScript)

クラスとインスタンスは異なる実体です 。
すべてのオブジェクトはインスタンスです。

クラス定義を用いてクラスを定義します。また、コンストラクタメソッドを用いてクラスをインスタンス化します。
コンストラクタ関数を用いてオブジェクトのセットを定義および作成します。

new 演算子を用いて単一のオブジェクトを作成します。
同じです。

既存のクラスのサブクラスを定義するクラス定義を用いて、オブジェクト階層を構築します。
コンストラクタ関数に結びつけられたプロトタイプとしてオブジェクトを割り当てることで、オブジェクト階層を構築します。

クラスチェーンに従ってプロパティを継承します。
プロトタイプチェーンに従ってプロパティを継承します。

クラス定義が、クラスの全インスタンスの全プロパティを定義します。実行時に動的にプロパティを追加することはできません。
コンストラクタ関数またはプロトタイプがプロパティの初期セットを指定します。個々のオブジェクトやオブジェクトのセット全体へ動的にプロパティを追加したり、それらからプロパティを削除したりできます。

クラスベース言語とプロトタイプベース言語 - オブジェクトモデルの詳細 - MDN



全てはオブジェクト

Javascriptで全てはオブジェクトで、クラスベース言語でいうところのインスタンスのみだと思えば良い。


JavaScript では、ほぼあらゆるものがオブジェクトです。

null と undefined 以外のすべてのプリミティブ型はオブジェクトとして扱われます。

すべてはオブジェクト - Working with Objects - MDN



オブジェクトはプロパティを持つ

オブジェクトは、変数や関数をプロパティとして持つことができる。


JavaScript のオブジェクトは、自身に関連付けられたプロパティを持ちます。オブジェクトのプロパティは、オブジェクトに関連付けられている変数と捉える事ができます。オブジェクトのプロパティは基本的に、オブジェクトに属するものという点を除いて通常の JavaScript 変数と同じようなものです。

オブジェクトとそのプロパティ - Working with Objects - MDN



オブジェクトの実装は、結局、HashMap(連想配列)

オブジェクトはHashMapなので、簡単な例としては、プロパティを配列の要素アクセスのように呼び出せる。

> "hoge".length

4
> "hoge"['length']
4


JavaScript オブジェクトのプロパティは、ブラケット表記法でもアクセスや設定ができます。オブジェクトは連想配列と呼ばれることがあります。

オブジェクトとそのプロパティ - Working with Objects - MDN


例えば、オブジェクトの宣言方法は、オブジェクト初期化子などと言われているが、結局よくあるHashMapの宣言と同じ。


var myHonda = {color: "red", wheels: 4, engine: {cylinders: 4, size: 2.2}};

オブジェクト初期化子の利用 - Working with Objects - MDN



メソッドと関数

オブジェクトのプロパティになっている関数がメソッド。


メソッドはオブジェクトに関連付けられた関数です。簡単に言えば、オブジェクトのプロパティのうち関数であるものがメソッドです。

メソッドの定義 - Working with Objects - MDN


ただし、メソッドはオブジェクトから参照されているだけで束縛されているわけではない。

よって、オブジェクトのプロパティとして宣言された関数(つまりメソッド)を、グローバルな環境で使えば通常の関数として振る舞う。


this は関数に「渡される」のであって、関数に固定されている訳ではありません。言い換えると、メソッドはそれをメソッドとして持つオブジェクトに束縛されているのではなく、オブジェクトによって参照されているだけです。

function Car(brand) {

this.brand = brand;
}
Car.prototype.getBrand = function() {
return this.brand;
}
var foo = new Car("toyota");
println(foo.getBrand()); // "toyota"
var brand = "not a car";
var bar = foo.getBrand;
println(bar()); // "not a car"

メソッドの束縛 - this - MDN



コンストラクタと関数

どの関数でもコンストラクタとして使用でき、new演算子とともに使用することでオブジェクトを作成する。


どの JavaScript 関数もコンストラクタとして使用できます。new 演算子をコンストラクタ関数とともに使用することで、新しいオブジェクトを作成します。

クラスの定義 - オブジェクトモデルの詳細 - MDN


なお、慣習として、コンストラクタとして使う関数は、通常の関数と区別するため、大文字で始める。


継承とプロトタイプチェーン

クラスの継承はプロトタイプチェーンという概念によって実現される。


// Object.prototype のプロトタイプは null です。

// o ---> Object.prototype ---> null
var o = {a: 1};

// 配列は Array.prototype(indexOf、forEachなどのようなメソッドを持っている)から継承します。
// a ---> Array.prototype ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];

// 関数は Function.prototype(call、bindなどのようなメソッドを持つ)から継承します。
// f ---> Function.prototype ---> Object.prototype ---> null
function f() {
return 2;
}

継承とプロトタイプチェーン - MDN



this について

プロトタイプベースだということで、問題になりやすいのがthisが何を指すか。

Qiita上でお二方が、thisは4種類あるよと書いてくれている。それぞれ比較するとわかりやすかった。

完全におんなじことを書いているわけではないが、だいたい同じことを書いている。せっかくなので対応表を書いておく。

Haru39流
vvakame流

メソッド呼び出しパターン
何かに所属している時のthis

関数呼び出しパターン
トップレベルのthis

コンストラクタ呼び出しパターン
コンストラクタ内のthis

apply,call呼び出しパターン
function#apply とか function#call とかで無理矢理変えられた時のthis

このあと、MDNのthisの説明も読むといいかも。


  • apply, callの時(4つ目)は明示的にObject、

  • メソッドの時(1つ目)は暗黙的にObjectを指して、

  • それ以外(2つ目)はグローバルオブジェクトになる。

  • newを使ったコンストラクタのthis(3つ目)については触れられてない
    (thisではなくnewの説明に書いてある)。