thisにセットされるもの

@__gfx__さんがhttp://d.hatena.ne.jp/gfx/20120223/1329996834:JavaScriptのthisの扱いが難しすぎる件という記事を書いていたので見てみました。

いろいろなケースでthisに何がなぜ設定されるのかが分からない、というものです。以下転載。

var o = {}, tmp;

o.f = function() { console.log(this.toString()) };

o.toString = function() { return "o" };
o.f.toString = function() { return "o.f" };
(function(){ return this })().toString = function() {
    return "global";
};


o.f(); // o
(o.f)(); // o

([o.f][0])(); // o.f
(new Array(o.f)[0])(); // o.f
([tmp  = o.f][0])(); // o.f

(tmp = o.f)(); // global
(function(){ return o.f })()(); // global

({x: o.f, toString: function(){ return "t" }}.x)(); // t

JavaScriptのエキスパートではない僕にも当然分からなかったので、@bulkneetsさんや@kazuhoさんのツイートを足がかりに考えてみました。

いろいろ考えたり試したりした結果こうであろうと考えたルール:

  1. 「o.f()」のようにオブジェクトを明示した上でオブジェクトのプロパティにセットされている関数を呼び出す場合、thisにはそのオブジェクトがセットされる。
  2. オブジェクトを明示せずに関数を呼び出す場合、thisにはグローバルオブジェクトがセットされる。
  3. 配列aについて、a[n]はaの"n"というプロパティを指す。
  4. 「(o.f)()」のように、関数を表すものが括弧で囲われている場合、その括弧は(thisを決定する上では)無視される。

JavaScriptのエキスパートから見ると回りくどい言い方になっているかもしれませんがご容赦下さい。)

これらのルールを使って説明していきます。

詳細な解説

o.f(); // o

ルール1よりthisにはoがセットされる。

(o.f)(); // o

ルール4より(o.f)の部分はo.fと同じと考えてよい。よってthisにはoがセットされる。

([o.f][0])(); // o.f

ルール4より([o.f][0])の部分は[o.f][0]と同じと考えてよい。またルール3よりこれは[o.f]という配列オブジェクトの"0"というプロパティを指す。さらにルール1よりthisには配列オブジェクト[o.f]がセットされる。

…となるはずなのに出力結果がo.fなのはどうして!?としばらく悩みましたが、実はo.fという出力はo.f.toString()によって出力されるのではなく配列[o.f]によって出力されるということに気づきました。通常配列のtoString関数は配列の中身をカンマ区切りで表示するため、要素がo.fだけの配列については出力がo.fとなるのでした。これについては@__gfx__さんも追記として書いていますが、@bulkneetsさんが

と書いているとおりです。実際に試してみると、

([o.f, "a"][0])();

はo.fではなくo.f,aとなりました。

(new Array(o.f)[0])(); // o.f

一つ前のケースと同様の考え方でthisには配列オブジェクトnew Array(o.f)がセットされる。

([tmp  = o.f][0])(); // o.f

一つ前のケースと同様の考え方でthisには配列オブジェクト[tmp = o.f]がセットされる。また、式tmp = o.fの評価結果はo.f(の指すもの)なのでthisには[o.f]がセットされる。

(tmp = o.f)(); // global

(tmp = o.f)の評価値はo.fの内容になるが、この式ではオブジェクトが明示されていない。よってルール2よりthisにはグローバルオブジェクトがセットされる。

なおこれは一見(o.f)()と同じように見えるのですが、@kazuhoさんが

とおっしゃっていたとおり、o.fとtmp = o.fは評価値こそ同じですが式自体の意味は全く違います。例えばo.fは左辺値となり得ますがtmp = o.fは左辺値にはなり得ません。そのためo.f = 1はOKですが(tmp = o.f) = 1はエラーになります。

(function(){ return o.f })()(); // global

ルール2よりthisにはグローバルオブジェクトがセットされる。

({x: o.f, toString: function(){ return "t" }}.x)(); // t

ルール4より({x: o.f, toString: function(){ return "t" }}.x)の部分は{x: o.f, toString: function(){ return "t" }}.xと同じと考えてよい。またこの式ではオブジェクト{x: o.f, toString: function(){ return "t" }}が明示的に指定されているのでルール1よりthisには{x: o.f, toString: function(){ return "t" }}がセットされ、その結果このオブジェクトが持つtoString関数が呼び出されてtが表示される。

おまけ。@__gfx__さんが再追記で書いている(o.f=o.f)()がglobalになったことの解釈ですが、ルール4より(o.f=o.f)はo.f=o.fと同じと考えてよく、またo.f=o.fの評価値はo.fの内容になり、かつオブジェクトが明示されていないため、ルール2よりthisにはグローバルオブジェクトがセットされることになります。

まとめ

今回の件は

でほぼ完全に説明し尽くされているといって良いと思います。よって

@kazuhoさんと@bulkneetsさんはすげー

が結論となります(笑)。

「JavaScript本格入門」を献本いただきました

これからお仕事でJavaScriptを書くことが増えてくるような気がした(気のせいではありません、きっと増えてくるのです!)ので、ざっとJavaScriptについて復習したいなーと考えていたところ、技術評論社様から「JavaScript本格入門」を献本いただきましたので読んでみました。(技術評論社様、ありがとうございました。)

JavaScriptをやっていると「こういう書き方をするとどうなるんだろう」「こういうことを実現するにはどうすればいいんだろう」といろいろな疑問が湧いてくるのですが、本書はサブタイトルに「“わかった気”で終わるのではなく、きちんと動くモノを作るために。」とあるとおり、各トピックについて単に表面的なところを舐めるだけに留まらず、こちらの「?」を先読みしてくれるかのごとく掘り下げて書かれている点に「なかなか良いな」と思わせられました。もちろんJavaScriptについてある程度詳しい層からするとそれでも物足りなさはあるのでしょうが、単なる入門書以上であることは間違いありません。

構成についても、JavaScriptの入門書というととかくブラウザ上での動作を前提とした書かれ方をしているものが多いのに対し、本書は言語としてのJavaScriptの説明とブラウザ上で動作させる時のトピックが分離されているため、初心者の方がJavaScriptをより正確に理解できそうだと感じました。

さらに生のJavaScriptを駆使するよりも手軽に動的かつ様々なブラウザに対応したWebサイトの構築を可能にするjQueryやその他の実用的なトピックについてもかなり紙面が割かれており、JavaScriptに不慣れな人でも本書を通読しておくことで現場であわてないで済むのではないかと思いました。

一点本書について難点を挙げるとすれば、typoや記述ミスが割と目立つことです。非常にもったいない*1。よく見ればそれが些細なミスだと分かるのですが、初心者にとっては何が正解か一瞬迷ってしまうと思います。その点は次の版できっちり修正されることを期待します*2

[2012-02-14 追記] 技術評論社様のサイトにtypoなどについての訂正がまとめられています。→こちら

*1:次回はぜひ出版前にレビューさせていただければきっちりご指摘させていただきます(笑)。

*2:献本いただいたのは初版ですので既に修正済みなのかもしれません。

30年前の僕へ

僕が始めてマイコンに興味を持ったのは小学校6年生の時だった。

仲の良かった友人Oに教えられたのがきっかけだった。O曰く「マイコンを使えばいろいろなゲームができるんだよ」。

当時は今のような百花繚乱な家庭用ゲーム機はなく、あったのはせいぜい数種類の組み込みのゲームを切り替えて遊べるゲーム機くらいだった。

そんなご時勢に、マイコンとやらを買えば数種類どころか数十種類、数百種類のゲームを遊ぶことができ、さらにプログラムというものを覚えればなんと自分でゲームを作ることもできるらしい。

当時の僕はマイコンに熱狂した。もっとマイコンの事を知りたくて、近所の本屋に行って「月刊マイコン」を買った。パラパラと中をめくってみるとなんだか難しいことがたくさん書いてあるようだったが、カラーページにゲームの画面がいくつか載っていてわくわくした。わくわくしながらも、レジに持っていく時、何か少し大人の世界に足を踏み入れた気分でドキドキしたのを今でも覚えている。

マイコンは小学生の僕にはおいそれと買える値段ではなかった。当時の花形はNECPC-8001SHARPのMZ-80シリーズ。ともに10万円以上する代物だった。

買うとしたらどれがいいだろう…。と毎日毎日飽きずにマイコン雑誌を眺め、MZ-80はTVとカセットデッキがついているけどVIC-1001はTVはともかくカセットデッキはどこについているのかな?写真に映っていないだけで本体の裏についているのかな?とか、PC-8001はカラーは出るけど音は貧弱らしい、MZ-80は音はいろいろ出るけどカラーはどうなのかな?とか、いろいろ想像を膨らませていた。

マイコンが欲しくて欲しくてたまらなくなった僕は、父に交渉することにした。今まで小遣いやお年玉を貯めた全財産(といっても4万円くらいだったと思う)を出すので足りない分を出して欲しい、と。父は「考えておく」と言ってくれたが、実は父の「考えておく」は僕がお願いを忘れるのを待つ時の常套手段だった。そのため念のためしつこくしつこくお願いしたのだが、ついに僕は父がマイコンを買うのに協力してくれる気はなさそうだと分かってしまい、父がいないところでものすごく怒った。

その様子を見ていた母が僕のために口添えしてくれたのか、結局父はマイコンを買うのに不足していたお金を出してくれることになった。

さて次の問題はどの機種を買うかだった。いろいろ悩んだ挙句最終的に候補に残ったのは、サウンド機能を持つがカラーが出るか分からないMZ-80と、カラーは出るが音が貧弱なPC-8001だった。どっちがいいか父に相談したところ、父は両方の広告を見比べて「MZ-80でいいんじゃないか」と言った。「CRTがついていると書いてある。これはきっと『カラーテレビ』の略だからカラーが出るんじゃないか」。CRTが「カラーテレビ」の略ではないことを知ったのはもう少し後のことだった。

とにもかくにも購入する機種はMZ-80に決まった。ただ新品は高すぎて手が出ないので、マイコン雑誌の「売ります買います」コーナーで手ごろな中古品を探すことにした。いろいろ探した結果、八王子の人がMZ-80K2を9万円で売りに出していたのでそれに申し込んでみたところ、幸いに応じていただけた。

マイコンを買いに行くことになっていた日曜日までは本当に毎日が待ち遠しかった。そしてついに日曜日。父と二人で八王子までマイコンを買いに行った。

譲ってくれる方は八王子の駅に車で迎えに来てくれた。その方のお宅でマイコンについて簡単にレクチャーを受けたのだが、その際なんとゲームが数十本入っているというカセットテープを一緒に譲っていただいた。マイコンが手に入るだけでもうれしかったのに、うれしいおまけつきで天にも昇る気持ちだった。

そしてマイコンを手に入れて、いただいたカセットテープに入っていた大量のゲームを友人Oとプレイし、ゲームを自分なりに書き換え(最初は砲台の数を増やすところから)、BASICを学び始め、マシン語を学び、友人Oとともに「将来はプログラマーになりたい」「将来はゲームを作る仕事をしたい」と夢を膨らませながら中学高校に進んでいった。

途中、決してまっすぐな道のりではなかったが、いろいろな経験を積み重ねた中間地点として今日この日にゲームを作る仕事に携わっていてしかもプログラマーとしても腕を揮えている(と思っている)ことを本当にうれしく思っている。そして人生の中で僕と関わりのあった全ての人達に感謝したい。

僕が初めて自分のマイコンを手にした日、それがちょうど30年前の今日。1982年1月31日。30年前の僕、今僕はゲームを作っているよ。

追伸:友人Oは僕より早くゲーム業界に入り活躍している。もちろん今でも僕の大切な友人である。O、僕にマイコンのことを教えてくれてありがとう!

というわけで今年も終わりですが

今年は例年にも増してブログを書けませんでした。が、決して今の仕事がマネジメントと調整業務ばかりだからネタがないというわけではありません。ネタはちょこちょことは出てくるのですが書いている時間がないといったところです。

でもまぁ明らかに言い訳ですので、来年はもう少しアウトプットできるよう頑張りたいと思います。めざせ1ヶ月に1エントリ(そんだけかよ)。

というわけで皆様良いお年をお迎え下さい。また来年もよろしくお願いいたします。

apple-touch-icon指定が効かない場合に確認すべきこと

iPhoneAndroidでは以下のような指定をHTMLに書いておくことによりホーム画面にショートカットを作成した時のアイコンイメージを指定できる。

<link rel="apple-touch-icon" href="/img/icon.png">

ところが開発環境などで試しているとこの指定をしているにも関わらず指定したアイコンイメージが使われないことがある。

その場合は開発環境でBASIC認証を使っていないかどうかを確認すると良い。

具体的にはアイコンイメージのURLにBASIC認証がかかっているとNGである。

仮にショートカットを作成する対象画面のURLに同じBASIC認証がかかっていて画面表示の際にIDとパスワードを入力済みであってもNGである。(このせいでハマった)

ちなみにAndroidは機種によって挙動が違うようで、BASIC認証がかかっていてもOKな機種があるかもしれないが、Galaxy S2ではNGだった。

条件構造で修飾されたmy文の振る舞い

以下のようなコードがどう振舞うのか良く分からなかった:

my $val = 1 if $flag == 1;

普通に考えるとif部分は「my $val = 1」全体にかかるはずなので、$flagが1でない場合は$valの宣言自体がなされないはず。
ということは宣言が実行時まで有効かどうか分からないということになるが、宣言はコンパイル時に判定されるべきな気がするので
なんか違和感がある。

というわけで調べてみた。

以下のperlのコードが動かない理由を教えて下さい。

このページのNo.4のりゅうさんの回答が参考になった。

http://perldoc.jp/docs/perl/5.10.0/perlsyn.pod :

注意: (my $x if ... のような) 条件構造やループ構造で修飾された my 文の振る舞いは 未定義 です。 my 変数の値は undef かも知れませんし、以前に代入された値かも 知れませんし、その他の如何なる値の可能性もあります。 この値に依存してはいけません。 perl の将来のバージョンでは現在のバージョンとは何か違うかも知れません。 ここには厄介なものがいます。

というわけで振る舞いは未定義であることが分かった。この書き方は使わないようにしよう。

Windows7で共有フォルダに接続できなくなった場合の対処法

Windows7で共有フォルダにアクセスすると、通常は認証ダイアログが表示されてそこで認証情報を入力すると接続されるはずなんだけど、なぜか認証ダイアログが表示されずしばらくするとエラーになるケースがあった。

いろいろ調べたところ、認証情報が保存されている時にそういう現象が起こることがあることが分かった。

認証情報(正確には資格情報というらしい)を削除してリトライしたところ、無事認証ダイアログが表示されるようになった。

視覚情報を削除するには、コントロールパネルから「ユーザーアカウント」→「資格情報マネージャー」の画面を開き、「Windows資格情報」のブロックで資格情報を削除したいネットワークコンピュータの行をクリックして「資格情報コンテナーから削除」をクリックすれば良い。