2008年02月07日

javascriptについてのメモ

思うところがあり、javascriptをいじっています。
javascriptは言語としてより、サイトを作る上での気の利いたTIPS♪みたいなのが多いので、適当なリソースが見つかりにくくて知りづらいなあとちょっと思います。それでもAJAXが一世を風靡して以降はかなりいろんなリソースが日本語でも手にはいるようです。それでもDOMの話などが解説に混じっていることが多くて……
とりあえず、DOMとかそういうゴチャゴチャしたのは置いておいて、演算子とか制御構文も多分大体わかるので置いておいて、全くわからないし使ったこともないプロトタイプベースのオブジェクト指向というのを知っておこうというのを目標にしてみました。
というわけで、わかったことを自分用に適当にメモ。間違っているかも。あとスクリプト部分に処理していないので読みづらいです。

まずjavascriptで関数を定義する方法は主に三つ。

function a(arg){hoghoge};
a = function(arg){hogehoge};
a = new Function(arg,"hogehoge");

三つはだいたい、同じ。ただし、三つ目はクロージャとかで上手くいかない。

javascriptのオブジェクトは全て連想配列(辞書)である。
メソッドは連想配列の値として代入された無名関数である。メンバは適当な値。例えば、
var obj = {member1:"a",
member2:100,
method1:function(){return "b"},
method2:function(){},
};
alert(obj.member1) // a
alert(obj["member1"]) // a
alert(obj.method1()) // b
alert(obj["method1"]()) // b

となると当然複数のオブジェクトで全く同じ無名関数を共有することもある。そのためメソッド内でオブジェクトのメンバを参照するために、thisというコンテキストオブジェクトを用いることになる。thisには関数を呼び出したオブジェクトが代入されている。
関数を呼び出したオブジェクトというのは

object.method(...) の object
func.call(object,...) の object
func.apply(object,[...]) の object
イベントが起きたときの、イベントハンドラをプロパティに持つイベントターゲット。

の四つの場合らしい。DOMと関わる最後のが分かりにくいなあ。
上の二つの場合を例にすると

var a = {member1:"a"};
var b = {member1:"b"};
a.print_member1 = function(){alert(this.member1)};
a.print_member1() // a
a.print_member1.call(b) // b

となる。
ブラウザ上で実行するとき、トップレベルではthisにwindowが代入されている。
alert(window == this); // true

オブジェクトの作り方は上記のようにリテラルで連想配列をつくることと、コンストラクタを使うやり方がある。
任意の関数はnew func(...)という形で呼び出すことでコンストラクタとして呼び出される。new func(...)で呼び出されたとき、まずthisにfunc.prototypeを暗黙に参照しているオブジェクトが代入される。で、適当に初期化をしたあと、返値としてthisに代入されていたオブジェクトが返される。
prototypeというのは全ての関数(Function)が持つメンバで、その関数をコンストラクタとしてつくられたオブジェクトはprototypeを暗黙に参照する。暗黙に参照するとは、オブジェクトが持っていないがprototypeが持っているメンバ、メソッドが呼び出された場合、そちらが実行される。ただし、代入など破壊的な操作に関してはそのオブジェクトでメンバがつくられる。全てのオブジェクトはObject.prototypeを暗黙に参照している。うん、訳わからん説明だ。

var Constructor = function(arg){
this.a = arg;
};
Constructor.prototype = {a:"hoge",b:"hogehoge"};

var a = new Constructor("aaa");
alert(a.a); // aaa;
alert(a.b); // hogehoge;
Constructor.prototype.b = "hogehogehoge";
alert(a.b); //hogehogehoge
Constructor.prototype.a = "hoge...";
alert(a.a); // aaa;
a.b = "bbb"
//破壊的な操作を行ったため、bというメンバがaに出来て、prototypeへの暗黙の参照がなくなる。
alert(a.b); //bbb
Constructor.prototype.b = "hogehogehogehoge!!!";
alert(a.b); //bbb

という感じか。
つまり、クラスは存在せず、コンストラクタだけが存在する。new でコンストラクタを呼び出すことで、prototypeを参照するオブジェクトが生成され、それをコンストラクタ内で加工した後、呼び出し元に返す。イメージとしてはprototypeをコピーして必要なところだけ代える、という感じ。
ただ、連想配列のメンバをprototypeでつくると、複数のオブジェクトで同じ物が参照される可能性があるので、メンバは基本的にコンストラクタ内で初期化するのが良い。一方メソッドをコンストラクタ内でつくると、コンストラクタを呼び出す毎に関数オブジェクトが生成されるので、プロトタイプにつくるのが良い。

Aを継承したBというオブジェクトのコンストラクタをつくりたいときは、
var A = function(){};
var B = function(){};
B.prototype = new A();

とする。このとき
var c = new B();
alert(c instanceof B) // true
alert(c instanceof A) // true
alert(c instanceof Object) // true
alert(B instanceof A) // false

あとAを関数を返す関数にすると、new A()で返ってくるのは関数オブジェクトになる。このとき同様のことを行うと
alert(c instanceof B) // true
alert(c instanceof A) // false
となる。ちゃんとthisに代入されているオブジェクトを使わないとダメみたい。


ブロックスコープはない。
var a = 1;
{
var a = 2;
};
alert(a); // 2

ブロックスコープを扱う場合は、関数を用いる。
var a = 1;
(function(){
var a = 2;
})(); //無名関数をつくり、その場で呼び出し
alert(a); // 1
うーん、トリッキー。新しいバージョンで導入されたletを使えば、このあたり色々楽に出来るらしい。

関数はクロージャになる。クロージャは環境(変数とか)を保持する。
var func = (function(){
var a = 1;
return function(){return a;}
})();//aの値を返す関数を返す関数をつくり、その場で呼び出し
//ここ return new Function("return a");だと最後の出力が2になる。
var a = 2;
alert(func()); // 1
となる。これを利用すると、プライベートメンバのような物が実装できる。
var Counter = function(){
var count = 0; //普通はthis.count = 0とする。
this.inclement = function(){count++;};
this.value = function(){return value};
}
var c = new Counter();
c.inclement;
c.inclement;
alert(c.value()); // 2
// alert(c.count); // Error

となって、幸せなときもあるようです。
しかしこのとき上手く継承を使う方法がよくわからない。Counterを継承するCounter2をつくるとき

var Counter2 = function(){}
Counter2.prototype = new Counter();

としてしまうと

var d = new Counter2();
var e = new COunter2();
d.inclement;
e.inclement;
alert(d.value); // 2
alert(e.value); // 2

というように、dとeが同じcountを参照してしまう。当たり前という気もするけど。コレをどうにかする上手いやり方はあるんだろうか。

以上javascriptのオブジェクト周りについてとりあえずわかったことを適当にまとめてみた。印象としてはクラスベースに比べてシンプルでスッキリしていて柔軟な分、凝ったことをやろうとすると捻ったやり方になるような感じがする。
コンストラクタとかは何とも無理矢理な感じもしてしまうけど、オブジェクトは全て連想配列である! というのは目から鱗が落ちた。
クロージャも何だか素敵ね。