または私は如何にして嫌うのを止めてJavaScriptを愛するようにならなかったか
あ、JavaScriptが大好きな人や得意な人は、このエントリーを読む必要はないので。いいね。断ったからね。
というわけでJavaScript。ヘンテコな言語であります。わけがわからない。実に多面的に何が何だかわからないので取っ付きにくい。できれば一生関わりたくない言語ではあります。どこかの隣国みたいですね。
しかしブラウザ上で何かを動かそうと思ったら、もうこれを使うしかない。政治的にFlashが抹殺された段階でそういう運命になったわけです。このまま衰退して消えればいいのにブラウザなんて。
などと暴言を吐いても現実は逃避できないので折り合いをつけねばなりませぬ。そこでJavaScriptのどこが嫌かを考え、自分なりに納得できる理由をでっち上げて少し好きになる努力をしてみました。彼にも良い所はあるの。って作戦。嫌いな人は参考にするといいよ。
Prototype
使い捨てられる試作品
生きるのは自分だろうか?
今は考えない‥考えない‥
”Prototype”石川智晶
石川智晶最高!でもJavaScriptのほうは…。さて今時のプログラミング言語がクラスベースなのに対して、JavaScriptはプロトタイプベースと呼ばれています。このプロトタイプというのは何でしょうか?
知らん。ググれば?
…。その前にクラスについて説明しましょう。ActionScript的な言語でゲームを作ったとします。そこに登場するザコキャラ。これをクラスで表現すると以下のとおり。
public class Zako exetnds Sprite {
public function Zako() {
…(なんか初期化する)
}
function move():void{
this.y += 8;
}
}
※コードは完成イメージです。実物とは異なります。
こんな感じで、変数やら関数やらをひとまとめにしたものを私はクラスと呼んでいますが他の人は知らない。
で普通はこのテキストをコンパイル(コンピュータがわかる言語に変換)してEXEか何かに固めて実行するわけです。この「EXEか何か」(エグゼドエグゼスじゃねーぞ)には、いまのZakoクラスの設計図が丸ごと格納されるので、別のところで、たとえばゲーム開始1秒後に以下のような感じで生成しちゃったり。
var zako = new Zako();
そうすると、プログラムのなかでZakoのクラス定義を見つけて、Zako関数をコンストラクタ(初期化用の関数)として実行するわけですよ。よく見ると「extends Sprite」なんて書いてある。つまりSpriteというクラスからいろいろ引き継いでるので親のほうの面倒もみなくちゃいけないね。そうやって先祖代々まで調べてメモリー上に実行用の命令を並べ、関数のエントリーポイントとかなんとか一切合切準備して、その先頭のアドレスを変数zakoに突っ込んで返すわけだ。いや、わけです。
すごいね。知らない間に沢山の人が働いてるんだね。当たり前か。でもこれ、すんごく重たい作業なんだよ。メモリーも食うし。パーティクルみたいなのでリアルタイムに大量生成すると遅いから、あらかじめプールして使いまわしたりするくらい。
さて、JavaScriptってのはブラウザ上のオマケだから(異論はあるでしょうとも)、たとえばテキストを表示してる最中にガチャガチャと初期化が始まって処理が止まっては困る。なのでクラスベースのようなコストの高い(処理が重い)言語の採用は避けたいが、なにか良い方法はないかなあという判断が働いたんだそうです。おじいちゃんが言ってました。そこで登場するのがプロトタイプ。
今度はJavaScript的なプロトタイプを利用して書いてみます。
function Zako(){
…
}
Zako.prototype.move = function(){
this.y += 8;
}
※コードはイメージでなんちゃら
Zakoという関数を定義して、それとは別にprototypeとかいうのを間に挟んだ変なものが定義されてる。この右側のfunction(){}てのは関数ですよという意味で、そのアドレスをZako.prototype.moveに代入しときますよ、って意味。関数ポインタですな。
JavaScriptの場合はコチラがコンパイルする必要はないので、このコードをScriptタグで囲んでHTMLファイルに書き込めば、開くとすぐに実行されます(ちょっとウソ)。ザックリと説明すると、ブラウザはまずScriptタグで囲んであるテキストを残らず全部読み込みコンパイル。その中で関数宣言的なもの以外を上から順に実行していきます(的な)。
つまり、クラスを書いてるつもりでも普通の関数としてメモリー上にどかんと生成されるわけです。実行はされないけど。だから、
var zako = new Zako();
と書いてzakoを生成した場合も、すでにメモリー上にあるfunction Zako()の中身をコピーして、Zako関数をコンストラクタとして実行してるだけ。いやまあさすがにそんなに簡単じゃなくて、なんたらチェーンとかデチューンとか色々と処理はあるけど、それでもクラスベースよりははるかに軽い処理。つまりプロトタイプをベースに新しいオブジェクトを作りますよと。それがプロトタイプベース。そして特長は「プロトタイプベースだと処理が軽くて初期化の時間も短い」
ということなんだね(くどいけど私の解釈だから)。
さてここからが本題だ。今までのは副題だ。クラスベースで生成したzakoオブジェクトには、move関数も含めたすべてがメモリー上に確保される。当然だね。じゃあzakoキャラが二匹になったら?そう、二匹分のメモリーが必要になる。関数も含めて一切合切。
対するプロトタイプは、実はzakoにはコピーされない。最初に全体を読み込んだ時の「Zako.prototype.move」という関数への参照が渡される。だから雑魚キャラをいくつ作っても、move関数はひとつしかないのでメモリーが節約できるわけだ。prototypeの下にぶら下がったプロパティは、Zakoで共通して使いましょう的な仕組みなんだね。もちろんどのオブジェクトからも、move関数はxxx.move()で実行できる。prototypeと書く必要はない。
結論、
「プロトタイプベースだとメモリーが節約できる」
実際、敵キャラなんかで個別に必要なのは位置とかライフとかのパラメータで、移動アルゴリズムなんて共通なんだから。そんなのを全部の敵キャラが保持しても無駄だし、使えるメモリーも少ないんだから共有しよう。ということ。
いろいろ調べるとなんかプロトタイプ最高!クラスベースくそ!みたいな話ばかり出てきて、そうかなあと感じてたから、こういう結論に(自分で勝手に)達したときはちょっと安心したんですよ。なるほど。ハード側の都合があるんで人間様が譲歩してくださいよと。そこでプロトタイプですよと。そう思えば少しは腹の虫も収まるというもの。でもないか?メモリー節約できるって聞いたらうれしくない?ないか。
クロージャ
あー、わからん。サッパリだよ。ちょっとwikiから引用。クロージャ (クロージャー、Closure) は、プログラミング言語において引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決する関数のことである。
ですって。もういいかな?こんなの使わなくていいよね?まあでも使うこともあるか。さて、クロージャのことは忘れて次の例を見てほしい。
function Zako(){
var life = 10;
}
var zako = new Zako();
ザコのライフは10よっ!Zakoオブジェクトを生成して、ライフを10に初期化してzakoに代入したわけだけど、このlifeどうやって参照するんだ?というかlifeって初期化後に消えるんちゃう?そうです消えます。初期化関数内のものはそこを抜けると消滅するのがルール。そこでプロパティに代入。
function Zako(){
this.life = 10;
}
だめじゃん。プロパティにしたら外から操作しまくれるじゃん。ワイルドすぎる。ライフを操作する関数を用意しないと。
function Zako(){
var life = 10;
function hit(){
life–;
}
}
はい。これで、zako.hit()でライフを減らせます。…ウソです。減りません。というか、そんな関数ありません。どこ行った?そうです。さっき言ったじゃない。初期化関数内のものは抜けると消えるの。冥府のものは持ち帰れない。このhit関数も、初期化の時は使えるけど(使ってないけど)抜けたら消えるの。
じゃあ、関数のアドレスを返すか。
function Zako(){
var life = 10;
function hit(){
life–;
}
return hit;
}
あー、これだとhit関数が返るだけだな。それはそれで面白いことができるんだけど、ここはオブジェクトにして返そう。function Zako(){
var life = 10;
function hit(){
life–;
}
return { hitme : hit };
}
これで、var zako = new Zako();
zako.hitme();
初期化関数内のものは消える定めなんだけど、それを参照している関数が存在していて誰かが覚えてると消えないってことなんだね。思い出って大切だ。
この例では変数lifeを減算する関数hit()をオブジェクトの形で返してる。そうするとZakoオブジェクトを生成したときにhitmeというプロパティの形でhit()関数への参照が手に入ると。ちなみに{}てのはオブジェクトを生成する書き方。
var hoge = {};
ってやると空っぽ(みたいな感じ)のオブジェクトが生成される。var fuga = { hage:0 }
ってやるとオブジェクトの生成とhageプロパティの追加が一度にできる。これだとfuga.hageが0になる。何の話だっけ?そうそうクロージャだよ。クロージャ。こういうのをクロージャと呼ぶ、んだろうか?知らない。自分で調べて。
で、これってクラス内にプライベートな変数を保持するためのアイデアなんじゃないの?そうだよきっと。JavaScriptにはPrivateってないから、それを実現するための苦肉の策なんだな。そうだろ。いやあ面倒だ。面倒だし使わない。全部プロパティとグローバル変数で実装しちゃえ。とは行かないんだよなあ…。
感想
「ブラウザという制約の大きい環境で動作する言語」と捉えればJavaScriptはそんなに悪くないと思う。理想的な言語が現実的な速度で動作するかどうかは別の話だし。ただクラスベースは古臭くてプロトタイプ最高!的なマッチョで声の大きい人がカッポしてるのはどうかなあ。むしろ、JavaScriptの言語仕様にないクラスを無理矢理実装したらこんなに複雑になっちゃった、ごめん。くらいなら良かったんじゃなかろうか。工夫した俺たちエライとか勘弁して。JavaScriptの言語仕様にしても、他の言語の経験から不可解な仕組みを類推できる部分が多いから、初心者がこの言語から入るとあまりの難解さにプログラミングが嫌いになるんじゃなかろうか。JavaScriptは簡単とか未来とか宣伝してる起業家がいるけど、スマートフォン向けゲームを作るためにこの言語だけを集中して覚えさせたら、コピペしかできない人を量産するだけだよ。絶賛してるマッチョな人たちにだまされないように。ありゃあ義務教育における英語の先生と同じ人種だ。そんなんじゃあ技術者は育たない。JavaScriptの経験者が増えない原因も考えようよ。
あとはそうだなあ。変数とかが基本はハッシュテーブルなんだと思えば理解しやすいかも。関数のポインタとか。それと他に何かあったかなあ。
[…] HTML5ゲームJavaScript苦闘編 « ど~でもインスタンス […]