prototype図示とnewの挙動
多くの方に読んでもらえたのが何より嬉しい。
ブログ、書いて良かった、純粋に。ありがとうございます。
でも、はてぶページとか社内とかで、
「でも...もう一歩しっくりこない」という声もあって。。
話をしてて、以下の2つが理解できると、
Javascriptのprototype指向がしっくりくる(かもしれない)ことがわかった。
で、僕なりにそれをまとめてみようと思った。(くどくない程度に^^;)
それは次の(1)と(2)の理解である。
(1)prototypeとオブジェクトの関連を図でイメージする。
(2)newの挙動を把握する。
この2つをきっちり理解すれば、
少なくとももう少しprototype指向がしっくりくると思う。
だから、前の記事でよく分からなかった人は、読んでいただければと思う。
題材は前回の記事と同じく、赤い箱と青い箱を取り上げる。
var Box = function(size){ this.size = size; } Box.prototype = { color: "red" }; var redBox1 = new Box(10); var redBox2 = new Box(11); Box.prototype = { color: "blue"}; var blueBox = new Box(12);
(1)prototypeとオブジェクトの関連
まずは、(1)prototypeとオブジェクト{}の関連を図でイメージする。
オブジェクトとprototypeの関係が直観的に理解できるかもしれない。
ポイントは、
redBox1, blueBoxは、BoxやBox.prototypeを見ているのではなく、 { color: ** }のオブジェクトを指している(参照をもっている)
ということだ。
クラス指向の考えを引きずると、redBox1, blueBoxは、
BoxとかBox.prototypeを指しているように誤解しやすい。
Box.prototypeは実行時に決まるオブジェクトを指している。
(ややくどいけど)だから、上のコードの後に以下のようにすると...
Box.prototype = redBox1.prototype; Box.prototype.color = "yellow"; alert(redBox1.color); //yellow alert(redBox2.color); //yellow alert(blueBox.color); //blue
(Box.prototypeが指すオブジェクトを青い箱から赤い箱に変えた。)
これらの結果が前より納得いくものとなっていたら幸いである。
(2)newの挙動
(1)のprototypeが理解できたら、あとはnewが理解できれば、
javascriptのprototypeに大してビビることはなくなると思う。
newに関しては、僕はこの記事がすごく分かり易かった
だから、nanto_viさんの文言をお借りし、書いてみる。
例えば、この新しい青い箱を作るという処理を思い浮べて、読んでもらえればとおもう。
var blueBox = new Box(12);
"new" の挙動
- 新しいオブジェクト{}を作成する
- 1で作成したオブジェクトのprototypeに、Box(関数オブジェクト)のprototypeが格納している参照を格納する。(上の例では、{color: "blue"}オブジェクトへの参照)
- 1で作成したオブジェクトへの参照を、関数Box内のthisにセットし、関数Boxを呼び出す。引数が記述されていれば(例では12), それを関数Box実行の引数とする。
- 1で作成したオブジェクトへの参照を返す。
正確な定義はひとまず置いておいて、これがnewの挙動である。
新しく作るオブジェクトの初期化を行いたい場合ば、
Boxの処理の中に this を記述すれば、初期化処理を行うことができる。
var Box = function(size) { this.size = size; };
Box.prototypeが指すオブジェクトに影響を与えず、
新たに作成したオブジェクトにだけ処理[ex.プロパティを追加]をしている。
こうした関数オブジェクトBoxはコンストラクタと呼ばれるが、
new との協業によって新たなオブジェクトを作成していることからも、まさしくコンストラクタである。
一方、クラスというものは存在しない。
実際Ecmascriptの仕様書にも、
コンストラクタは明記されているが、クラスという言葉は出てこない。
(全部隅から隅まで見たわけではありません、、、^^;)
一般に言うクラスの役割をするのは、
まさにBox.prototypeが指しているオブジェクトであり、
これがprototype指向の核なのかな、と思った。
以上、分かっている人には当たり前の、つまらない説明かもしれないが、
少しでも納得した人がいてくれたら、幸せだなぁと思います。
prototypeを把握した上で、
prototype.jsを読んでみるのも面白いと思います。(extendとか)
Javascript初心者からみたprototype
http://d.hatena.ne.jp/amachang/20070413/1176421425
勉強会のあと、そしてこの記事の後、
もう一度自分の中でprototypeについてまとめてみた。
下の実行結果を見て、首をかしげた人は是非とも読んでいただければと思う。
(って、首をかしげるのは僕だけか?!)
var Box = function(size){ this.size = size; }; Box.prototype = { color: "red" }; var box = new Box(10); alert(box.color); // red Box.prototype = { color: "blue" }; alert(box.color); // red var box2 = new Box(11); alert(box2.color); // blue Box.prototype.color = "yellow"; alert(box.color); // red alert(box2.color); // yellow
僕は最初、この結果を理解することができなかった。
というのも、Boxというクラスから作られた2つのオブジェクトにおいて、
クラスの元となるオブジェクトおよびクラス変数を変更しても、
想定する挙動を示してくれないと思ったからだ。
以下が想定してた挙動との相違点。
#1 Box.prototype = { color: "blue" }; alert(box.color); // red (想定してたのは blue) #2 Box.prototype.color = "yellow"; alert(box.color); // red (想定してたのはyellow) alert(box2.color); // yellow (想定とおり)
この相違によって、結果的に僕のJavascriptに対する理解を深めることができた。
それは、
Box.prototypeが指しているオブジェクトが不変であるという間違った認識に気づいたことであった。
より分かり易い例を下に示す。
var Box = function (size) { this.size = size; }; Box.prototype = { color: "red" };
Boxのprototypeには「赤い箱」がセットされる。
この後、Boxのオブジェクトを作成すると、「赤い箱」が作成できる。
var redBox1 = new Box(10); var redBox2 = new Box(11);
続いて、Boxのprototypeに「青い箱」をセットしよう。
(この処理をやってはいけないかどうかの問題はさておいて...)
そして、Boxのオブジェクト(青い箱)を作成する。
Box.prototype = { color: "blue" }; var blueBox = new Box(12);
確認すると以下の結果が得られる。(当然と思われるかもしれない、、)
alert(redBox1.color) // red alert(redBox2.color) // red alert(blueBox.color) // blue //redBox1.__proto__ => { color: "red" } //redBox2.__proto__ => { color: "red" } //blueBox.__proto__ => { color: "blue" }
続いて、Box.prototypeのcolorプロパティを"yellow"に変更する。
Box.prototype.color = "yellow";
Box.prototypeには「青い箱」がセットされており、
その青い箱を元に、blueBox というオブジェクトが作られていたことを思い出す。
つまり、無名オブジェクト{ color: "blue" }には今、
Box.prototype と blueBox.__proto__ でアクセスすることができるのだ。
そのcolorプロパティを "yellow"で上書きするのがこの処理である。
alert(blueBox.color) //yellow
青い箱には全く関連のないredBoxには影響はない。
alert(redBox1.color) // red alert(redBox2.color) // red
首を傾げていた方はすっきりしたかもしれない。
つまり、new Box から作成されたオブジェクトは、
そのときのBox.prototypeに入っていたオブジェクトを常に(prototypeとして)持ち続けるということだ。
そして、Box.prototypeに新しいオブジェクトを入れ替えたところで、
既にnew されて作成されたオブジェクトのプロパティには何ら影響はない。
もちろん、Box.prototypeに入っているオブジェクトと、そのオブジェクトのprototypeが一致している場合には、
(Box.prototypeに全く新しいオブジェクトを入れ直してない限りは)、
Box.prototypeに処理を加えれば、オブジェクトのprototypeにも処理を加えることになる。
だから、「青い箱」だけが「黄色い箱」に変わることができたのだ。
弾さんがおっしゃられていたObject.prototype = {} を(特に大規模開発で)使用すべきでないというのは、
prototypeに異なるオブジェクトを代入すると、
代入前にnewされて作成されたオブジェクトと、代入後にnewされたオブジェクトでは全く関連がなくなる、
ということを懸念してのものだったのかな、、と初心者なりに解釈してみた。
でも、Object.prototype = {}はやっぱり見やすいので、管理できる範囲であれば使いたい、とも思う。
そして何より、Javascriptギザオモシロス。
文字クラスとutf8フラグ
文字の集合を1文字として表現する文字クラス。
ASCIIは基本うまく動くんだけど、
Unicode文字に、標準ではうまく動作してくれなかった。
以下その例。
{ my $HIRAGANA = 'あいうえお'; my $KANJI = '漢字'; if ($HIRAGANA =~ m{\A [あ-お]+ \z}xms) { print STDERR qq{あ-おマッチ\n}; } if ($HIRAGANA =~ m{\A [^あ-お]+ \z}xms) { print STDERR qq{あ-お以外\n}; } if ($KANJI =~ m{\A [^あ-お]+ \z}xms){ print STDERR qq{(漢字)あ-お以外\n}; } }
結果
あ-おマッチ
漢字に対して、あ〜お以外の1文字以上から構成されているはずの判定がうまくいかない。
この問題は、use utf8 とすれば、うまくいくようになる。
{ use utf8; my $HIRAGANA = 'あいうえお'; my $KANJI = '漢字'; if ($HIRAGANA =~ m{\A [あ-お]+ \z}xms) { print STDERR qq{あ-おマッチ\n}; } if ($HIRAGANA =~ m{\A [^あ-お]+ \z}xms) { print STDERR qq{あ-お以外\n}; } if ($KANJI =~ m{\A [^あ-お]+ \z}xms){ print STDERR qq{(漢字)あ-お以外\n}; } }
結果
Wide character in print at not.pl line 14. あ-おマッチ Wide character in print at not.pl line 22. (漢字)あ-お以外
utf8フラグが付いたまま出力されていることによる、Wide Character warningは無視するとして、
判定動作がうまくいっていることがわかる。
chrome内のjarファイルを拡張機能の名前で解凍するperlスクリプト
firefoxの拡張機能を作りたいと思っているのですが、
もっといろんな拡張機能の中身を効率的に見たい!!
でも、ディレクトリ名が{45qiks8763jkyy}みたいな名前で全然わかりにくい。。
ということで、
/tmp/extensions の中の firebug といった拡張機能の名前のディレクトリに
firebug.jarを解凍してくれるperlスクリプト。
ただし、Linuxでしか動作しません。動作確認はFedoraCore6のみ。
それぞれの環境に合わせて適宜変えてください。^^;
って、機能のわりに制約多すぎてあんま存在意義なさそ。。まぁいいやw
#!/usr/bin/perl use File::Find; use File::Path; if (scalar @ARGV < 1) { die "You should input first arguments, firefox extensions directory."; } my $EXTENSION_DIR = $ARGV[0]; my $OUTPUT_DIR = $ARGV[1] || '/tmp/extensions'; my $DELETE_FORCE = $ARGV[2] || 0; find(\&unzip_jar, $EXTENSION_DIR); sub unzip_jar { my $jar_path = $File::Find::name; my $jar_name = $_; if ($jar_path =~ /extensions\/[^\/]+\/chrome\/[^\/]+\.jar/) { my ($ext_name) = $jar_name =~ /(.*)\.jar/; my $output = "$OUTPUT_DIR/$ext_name"; if (-d $output) { return if !$DELETE_FORCE; } else { File::Path::mkpath($output) unless (-d $output); system("unzip $jar_path -d $output"); } } }
使い方
./unpack_extensions.pl /home/develop/.mozilla/firefox/qq23wse.default/extensions /tmp/extensions
というぐあいに、
第1引数に拡張機能までのパス、第2引数に出力ディレクトリを指定してください。
ちなみ、FedoraCore6にはunzipがデフォルトでは入ってません。
また、yumレポジトリにも無いので、
FedoraCore5用のパッケージをインストールする必要があります。
ftp://ftp.riken.go.jp/fedora/core/5/i386/os/Fedora/RPMS/lha-1.14i-19.2.1.i386.rpm
要注意。
__parent__に初めて触れてみますた
サイ本のp.206に載っている__parent__プロパティについて。
名前からしてもわかるように、
継承チェーンのkeyとなる__parent__なんですが、
p.207の例(__parent__に親オブジェクトと異なるオブジェクトを代入)
を忠実に実行しても、記載通りの結果になりませんでした。。
結局、(firefoxでは?) __parent__に代入できないという結論になったのですが。
この実験の中で、色々おもしろい結果が得られたので、少しご紹介。
例1:
var f1 = function temp () { var a = 1; alert(a); }; var f2 = function() { var b = 1; alert(b); } alert(f1.__parent__); //(1) alert(f1.__parent__.toSource()); //(2) alert(f2.__parent__); //(3) alert(f2.__parent__.toSource()); //(4)
とすると、
(1)の結果
[object Object]
(2)の結果
({temp: (function temp(){var a=1;alert(a);})})
また、
(3)の結果
[object Window]
(4)の結果
{}
わかったこと
var f1 = function temp ()
var f2 = function()
上の2つの書き方では、継承ツリーの構成が1段違って、
f1では、tempというプロパティをもったオブジェクトが__parent__入っているが、
f2では、__parent__にはWindowが直接入っている。
例2:(書きかけ)
javascriptの最適化について。(まだ十分理解できてない。。今日勉強!w)
id:amachang ありがとうございました! m(_ _)m
ソーシャルブックマークをGoogle検索のデータモデルに付与したいな
はてぶにはじまり、SBM(social bookmark)ってこつこつしてるけど、
読み返す機会がなかなか無いことにやや不満な今日この頃です。
ブックマークの良さって、いつでも読み返すことができるように、っていうよりは、
(特に開発系だと)おもしろいネタを見つけたときに、
『あ、おもしろいけどすぐにはアクションできないや、でも、このまま手放すのも...
ブックマークしとけば、後でいつでも見れるし、なんか自分の手中にいれた気がするぅ』
ってブクマして満足満足っていう要素も多かれ少なかれあるんじゃないかな。
でも、なかなか読み返せないのも事実。
で、逆の発想というか、そもそも読み返さないこと前提にしちゃっていいんじゃない?
キーワード検索したときに関連するブックマークURLが上位に表示されば良さそうだし。
new_page_rank = google_page_rank + hatebu_rank( keyword ) + ....
で検索結果だしてくれるような、エクステンションいいなぁと。
(はてなツールバーの検索窓で検索しても、自分のはてぶの結果が反映されてなさそーでしたし。)
こうすると、ソーシャルブックマークって、
世の中のサイトに自分の嗜好をどんどんどんどんタグ付けしてく感じ。
で、そのタグ付けした要素に重み付けされた、パーソナルな検索結果がでてきてvivaと思うのです。
データ量は、世界中のデータ×世界人口とふくれあがりそうですが...
っと、SBMを最大限活用できてない自分へのフラストレーションを吐いてみた。
...お腹減ったし帰ろう
与えられた値が数値かどうかの判定 (NaN)
こうやるのが正。かな?
if ( isNaN( parseInt( number ) )
isNaNは引数がNaNかどうかをチェックする関数なのですが、
NaN(Not a Number)って何??といったら、NumberオブジェクトのNaNプロパティらしいです。
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Properties:NaN
そして、このプロパティの値は、代入できないし、比較できないのだそうです。
NaN is always unequal to any other number, including NaN itself; you cannot check for the not-a-number value by comparing to Number. NaN. Use the isNaN function instead. NaNはどんな数字に対してもイコールではありません。たとえ、自分自身に対しても。 だから、NaNを数字と比較しちゃだめです。変わりに、isNaN関数を使ってね。
だから、これもこれもだめ。
if ( parseInt( number ) == Number.NaN )
Number.NaN == Number.NaN // false
なるほど。