JavaScriptでクラスの継承的なことする
Underscore.jsに代表されるように、最近のJavaScriptでのコーディングは関数型(っぽく?)で記述するのが流行しているようだ。いまいち流れについていけていない私は、未だに手続き型、オブジェクト指向で記述しているが、これは果たして悪いことなのだろうか。オブジェクト指向と関数型のどちらが良いのかの議論はさておき、今回はオブジェクト指向に関連するお話。
JavaScriptはオブジェクト指向ではなく、プロトタイプベースの言語だと言われている。
プロトタイプチェーンという仕組みはなかなか面白い。
最初はどうにもけったいな代物だなという印象でよく理解できずにいたが、ありがたいことにプロトタイプチェーンの仕組みについてはいろんな人が解説してくれている。
・JavaScript:プロトタイプチェーン詳解!prototype と __proto__ について
http://www.sirochro.com/note/js-prototype-and-proto/
・Google流 JavaScript におけるクラス定義の実現方法
http://www.yunabe.jp/docs/javascript_class_in_google.html
・そんな継承はイヤだ - クラス定義 - オブジェクト作成
http://qiita.com/LightSpeedC/items/d307d809ecf2710bd957
他、2, 3サイトの解説を読んでようやく理解できたような気がしているところ。
よくプロトタイプチェーンの解説に出てくる __proto__ というやつは実はECMAの仕様として決められているものではなく、実際にプロトタイプチェーンをさせるには、Object.setPrototypeOf というのを使ってやると良いらしい。しかし、setPrototypeOf はES6で定義された新しい規格なので、古いブラウザーは対応していないらしい。
ES5では Object.create を使ってプロトタイプを自前でコピーするのが定石だったとか。
それよりも更に古い環境では、自前でごにょごにょしてやる必要があるとかなんとか。
ということで、以下のように書いてみた。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <title>プロトタイプチェーンテスト</title> <script> // クラス継承関数 function inherits(childCtor, parentCtor) { // ES6 if (Object.setPrototypeOf) { Object.setPrototypeOf(childCtor.prototype, parentCtor.prototype); } // ES5 else if (Object.create) { childCtor.prototype = Object.create(parentCtor.prototype); } // legacy platform else { function tempCtor() {}; tempCtor.prototype = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype; childCtor.prototype = new tempCtor(); childCtor.prototype.constructor = childCtor; } }; // 基底クラス var Parent = function() {}; Parent.prototype.m1 = function() { console.log("method1"); }; Parent.prototype.m2 = function() { console.log("method2"); }; Parent.prototype.m3 = function() { console.log("method3"); }; // サブクラス1 var Child1 = function() {}; inherits(Child1, Parent); Child1.prototype.m1 = function() { console.log("Child1.method1"); }; // サブクラス2 var Child2 = function() {}; inherits(Child2, Parent); Child2.prototype.m2 = function() { console.log("Child2.method2"); }; Child2.prototype.m3 = function() { console.log("Child2.method3"); }; // テスト実行 $(function() { var c1 = new Child1(); c1.m1(); c1.m2(); c1.m3(); var c2 = new Child2(); c2.m1(); c2.m2(); c2.m3(); }); </script> </head> <body> </body> </html>
とりあえず Mac Safari 10.0.1 では、自作 inherits 関数の if, else if, else いずれのコードでも動作することは確認できた。
なんか不思議な感じではあるけれども、とにかくこれでクラス的なものは作れるし、オーバーライドもできる。
めでたしめでたし。
ところで、ES6では言語仕様としてクラスがサポートされているので、JavaやC#に慣れている人からしたらそっちを使う方がだいぶ良いのだろう。かくいう私もその1人であるのだが。聞くところによるとES6のクラスはただのシンタックスシュガーだそうで、内部はこういう仕組みで動いているというのは理解しておいて損はなさそうではある。
そんな感じで、ちょっと使ってみる分にはなかなか面白いおもちゃではあるが、それでもこういった動的型付け言語のキモい動作にどうしても馴染めないという人は、TypeScript とかを使って「静的型付け言語をコンパイルしてJavaScriptを生み出す」という生き方をしていくのが良いのかもしれないね。
JavaScriptがここまで流行ってしまったらもう仕方ないんだね。