読者です 読者をやめる 読者になる 読者になる

Rubyばっか書いてる人のためのJavaScriptオブジェクト指向入門

JavaScript Ruby

まずRubyでのオブジェクト指向と言語要素に慣れている場合はJavaScriptオブジェクト指向でかなり違和感を覚えるような気がします。

  • JSは全てがオブジェクト (プロトタイプベース:後述)
    • なんかRubyの言ってることと似ているが伝えたい意図が全く違う
    • JSのオブジェクトの構造はRubyでいうハッシュのようなもの

JSの中身空っぽ最小オブジェクト

var obj = {}

以下みたいな感じで、key値からvalueが参照される。ハッシュじゃんこれ。

var obj = {
  key: value
}

JSの関数は「第1級オブジェクト」。だいたい「値」ってことと同意。function() { ... }は別にその他の数字とか文字列とかと扱いに変わりはないないっすよという話。

var f = function() { ... }

この「関数=値」を前提にすると、JSのオブジェクトでメソッド(的なもの)を定義できることも納得する。

var obj = {
  method_name: function() { ... }
}

なんかすごい不器用だけど、なんかかわいいな。。。

クラス プロトタイプチェーン

  • JSにクラスはない
    • Rubyならクラスもオブジェクトだね

じゃあどうするか。

プロトタイプチェーンを使う(ことにした。)

「別に車の設計図なんてなくても、今ある車を真似して作ればいーじゃん笑」

「クラスなんて設計図なくても、今あるオブジェクトを真似して作ればいーじゃん笑」

JS野蛮だな。。。

var objA = {
  name: "とつ"
  say: function() {
    alert("I love" + this.name);
  }
}

var objB = {}
objB.__proto__ = objB;

objC.say // 呼べちゃう

もちろんプロトタイプチェーン(Rubyとかのオブジェクト指向でいう継承)のネストを深くすることもできます。

このプロトタイプチェーン、Rubyメソッド探索と手法は似てます。深くネストされてるときは、その継承チェーンを辿って行って一番浅いところにあったらそれを実行します。JSのプロトタイプチェーンも同じ挙動なようですね。

オブジェクトの生成

実は、上のプロトタイプチェーンの__proto__ってやつはあんまり使われんみたいです。他のオブジェクト指向言語やってる側としては分かりやすいんだけどなー。

その代わりにJSでは、以下のようにオブジェクトを生成します。

var o = new Person('totz')

ありゃ、これはRubyでいうClassクラスのクラスメソッドであるClass.newに似ています。違うのはJSではnewはオブジェクト生成ように定義された予約語であるということでしょうか。

よく見るオブジェクトを生成するコードの例

// コンストラクタの定義
var Person = function (name) {
  this.name = name;
}
// prototypeの拡張
Person.prototype.sayHello = function() {
  alert('Hello ' + this.name);
}
// 利用
var person = new Person('totz');

上記のコンストラクタの定義部分とprototypeの拡張部分をふくめて擬似的に「クラス定義」と呼ぶ場合が多い。コンストラクタは、Rubyでいうinitialize()ですね。

ただ、前述のスライドのようにここはクラス定義ではなくJSが本当にやっていることに基づいた方がそう書く意味も理解しやすくなりそうです。

newは以下のことをしてくれてます。

  • オブジェクトの生成
  • 継承
  • 初期化

擬似コードで表してみると、newの挙動がかなり理解しやすくなると思う。

これが ( ^ω^) ⊃)(⊂

var o = new Person('totz')

こうじゃ ( ^ω^) ≡⊃⊂≡

var newObj = {}
newObj.__proto__ = Person.prototype; // Person.prototypeの特徴をプロトタイプチェーンでnewObjに継承
Person.apply(newObj, arguments); // newObjをthisにコンストラクタ関数を実行
return newObj;  // 出来上がったオブジェクトを返す

ということでprototypeは、

  • プロトタイプオブジェクトの控え室
  • newとコンストラクタ関数でオブジェクトを生成するときに、protoに代入される

という使われ方をするのですね。

this

ということで、どうしても理解しておきたかったJSのthisの挙動について。

なるほどこれはRubyでいうselfだな(ドヤ)となっていたらあっというまに落とし穴にはまってしまいます。

「関数はオブジェクトに束縛されない」

なるほど、メソッドじゃなくて関数なのか。。。

var obj = Person() {
  key: sayHello {
    alert('hello');
  }
}

これは、オブジェクトがメソッドを保持しているわけではなく、その関数はそのオブジェクトによってただ参照されているだけなのですね。 => 渡すときには、メソッドを渡しているのではなくその参照を渡しているだけ!

では、thisとは、、、

「関数コール時に、その関数が所属していたオブジェクト」

なんですね。レシーバがないときは、JSでは普通ブラウザ実装の場合 window オブジェクトになります。Rubymainオブジェクトっぽい。

つまり、thisは呼び出し時に決定されます。

参考資料

以下のスライドは他の言語のオブジェクト指向に馴染んでいる人にはまじで参考になります。

http://www.slideshare.net/yuka2py/javascript-23768378