javascriptの最近のブログ記事



盛り上がってるところ*1に水を差すようですが、


気になって仕事が手につきません。


さっき前の席の人に「なんでニヤニヤ*2してるんですか?」って聞かれました。


そろそろお昼なのに、食事も喉を通らないかもしれないので、


書いてしまいます。



はてダで Javascript を実行するという件について。



先日、自分のはてなダイアリーにブログパーツを設置できるようになったので、事実上の JavaScript 解禁になりました。


ということで、JavaScript の eval を貼り付けるガジェットを Google Gadgets で作ってみました。


てっく煮ブログ - はてなダイアリーで JavaScript eval


やりたくなる気持ちはわかります。


僕も id:amachang さん(面識も本題に関係もないのに名前出してすいません)とかのスクリプトとか、その場で実行したいです。


でも、外部から来た変数に eval したらいかんと思うのですよ。



↓例えばこんなことできちゃう


http://gmodules.com/ig/ifr?url=http://nitoyon.googlepages.com/js_eval.xml&up_eval=alert(’bad%20script’)&synd=open&w=320&h=50



いきなりこんな画面でボタン押す方がおかしいですが、


このガジェットを使用することで



「任意の人が自分の手を汚さずに(リンクを張るだけで)任意のコードを


実行させることができる」



んじゃないかと思います。


自動実行とかもやろうと思えばきっとできちゃうわけですからね。



代案があまり思いつかないですが、



  • 任意の人が

  • リンクを張るだけで


の部分を何とか出来そうな気がします。


トークンとセットじゃないと eval しないとか。



個人的には、外部から来た変数への eval は御法度だと思ってるんですが、


実際のところはどうなんでしょうね。




*1:はてブ http://b.hatena.ne.jp/entry/http%3A//d.hatena.ne.jp/nitoyon/20070820/javascript_eval_on_hatena_diary


*2:ニヤニヤしてたのは、やらしいことを考えていたからです




CakePHP をいきなり実践投入して2ヶ月程たった。


最近は Web アプリ作る時に当たり前のように Ajax 使うし、


それを当たり前のように要求される。


(要求されるのはほとんどがアニメーションとかの部分であって、特に ajax ではないけど。)



で、それを当たり前のようにこなすに当たり、CakePHP は便利なんだけど、


何が便利かっていうと、 決して php から Ajax コードを記述する、


Ajax helper があるからではない。



せっかくだから Ajax helper を何度か使おうとしたけど、


結局全く使わなかった。(使えなかった)



php から javascript を記述しようとすると、痒いところが多すぎる。


じゃあ CakePHP は何が便利かっていうと、


controller 内 の action で、



$this->layout = '';

こうするだけで、簡単に layout なしの HTML パーツを生成できるから便利。


それを javascritp で受け取って表示するコードなんて、すぐ書ける。




  • HTML の生成は PHP が行う

  • javascript はそれを受け取って表示する

  • それ以外のアニメーションを含む HTML の加工は javascript が行う



それって当たり前のことなんだけど、改めてそう決めておけばメンテナンス性が落ちることもない。ハズ。


下手に Ajax helperを使ってしまうと、


どうしてもある部分では PHPで、ある部分は javascript に書かれてて、


しかもその境界があいまいになっちゃったりする。


関係ないけど javascript HTML テンプレートとか使い出したらいよいよ危ない。



html helper もいろいろあるけど、


html タグを書きたくない病の人向けすぎる。 form 関連だけで十分。


div タグとか img タグとか、そんなもん手で書けばよいと思う。



最近のデザイナーさんは smarty 混じりの html くらいなら普通に手を入れてくれるし。


僕のまわりのデザイナーさんが優秀なだけかもしれんけど。



それが smarty じゃなくて php コードだったらまた話は違ってくると思う。


確かめたわけじゃないけど。


なんでかっていうと、 「<?」これとか「?>」これとかが ぱっと見で html タグと区別しづらいから。


僕自身、PHP 混じり HTML 文見るとイラっとするし。


だから、PHP プログラマはできるだけ HTML 書きたくない病にかかったりするんだと思ってる。




parseInt に第2引数があるって知りませんでした。


なんかおかしいなーとは思ってたけど。


0から始まったら 8進数として解釈って、あんた。


なんておせっかいな。



「こんにちは。」から始まったら日本語と解釈するくらい横暴だ。


いや、それは合ってる。



「こんにちはこんにちは。」から始まったらはまちちゃんと解釈するくらい横暴だ。


いや、それも合ってる。




JavaScript Template(JST) を使って、HTML中に直接テンプレートを記述する方法


は問題がありました。



記述したテンプレートを innerHTML として取得すると、


ブラウザがそれぞれ勝手な解釈をして「正しい」html に書き直そうとします。


結果、少なくとも以下の問題が発生します。




  • 不等号が htmlentities に変換される



{if hoge>2}${hoge}{/if}

みたいな非常によくある if 文が、



{if hoge &#60; 2}${hoge}{/if}

↑こうなって parse error。


ただ、これくらいならソース弄って 置換してやれば問題ないです。



  • table のloop とか、実質無理。



<table>
{for val in data}
<tr><td>{val.title}</td><td>{val.value}</td></tr>
{/for}
</table>

↑こんなことしてテーブル出力したいじゃないですか。


でも、IE 様の innerHTML は </tr>と<tr>間の記述を無視してくれます。


そんな IE 様でも動かすにはこんな感じ



<table>
<!--{for val in data}-->
{for val in data}
<tr><td>{val.title}</td><td>{val.value}</td></tr>
{/for}
<!--{/for}-->
</table>

IE様 では innerHTML でも comment は 無視されない。


firefox では、innerHTML では comment は無視される。



こういうの、hack っていうの?


やってられるかっつうの。


ie、バーカ。バーカバーカ。



で、結局推奨通り 見えない textarea に入れて、value を取得することになるわけですが、


それじゃ意味ねえのよ。



デザイナさんと共同作業したいから、本来あるべきところにあってほしいわけで。


textarea に入れるくらいなら、その意味では、使わない方がマシ。


javascript でチマチマ html 組み立てる手間は省けるけどさー。



サーバサイド javascript って、そういえば昔あったなー。


それで何とかなるのかな。とも思ったけど、もう心が折れました。




もはや当たり前となった javascript とか ajax とか使って


ページ遷移しない web アプリの弱点として、


ブラウザの戻るボタンは罠問題があります。



ブラウザの「戻る」は生理的に染み付いてるものなので、


今さら注意書きで「使っちゃだめよ」とか言っても無駄です。



また、ページ遷移しない場合、パーマリンクどうするよ。って話にもなりますね。



という需要で、ajax の戻る対策をしたライブラリはいろいろあるわけですが、


ちょっと思うことあって自分でも作ってみました。


大いに参考にしたのはこちら。


Ajaxと戻るボタン・ブックマーク



こちらがさらに参考にしてたのはこちら。


location.hashを使ったセッション復元



あと、safari 対応にあたり、SWFAddressも参考にしました。


こっちは flash で同様のことを実現してます。



IE, Firefox については、先のみかログさんの言ってる通りやってます。


で、safari に関しては、history.length が肝っぽい。



SWFAddress のぞいたら、history.length を元に cookie に書き込んだり、


見えない form 作って submit したりうにゃうにゃやってましたが、


hisotry.length をキーとした配列に、location.hash 突っ込んでいったら事足ります。



で、さらに作り直す上で実現したかったのは、以下



  • callback の登録をもちょっとやりやすく

  • safari 対応

  • &以降を get 変数 like に扱う

  • クロスブラウザ対策に Factory メソッドパターンを使う


javascript で factory メソッドを使わない理由


このエントリで書いた、クロスブラウザ対策に Factory メソッドパターンを使う有用な例がこれかなーって思いました。


history 周りの挙動はブラウザによって全く異なるので、


ここはハードコーディングでよいのです。多分。



動作サンプル



万が一使ってみた方おられましたら、フィードバック的なものをいただけると超うれしいです。


あ、YUI依存あります。


extend くらいにしか使ってないので、自分好みのフレームワークに入れ替えたらよいと思います。




ここんとこ漸く javascript 頭になってきて、


チームの自分より詳しい人にも教わってるうちに、


やっとこの人の言う事がわかるようになって来た。



Function.prototypeを拡張して遅延実行を実現する


call と apply がムニャムニャムニャな人はこれを読むとよいです。



* call は固定引数


* apply は可変引数



で。


this はどこ問題


この問題の正解がやっとわかった。



hoge っていう変数にインスタンス化したクラスの fuga っていうメソッドを、


setTimeout で呼び出して、その中で this 使いたかったら、こうするのだ。



こうするのだ。とか書いてるけど、こんなの文章でもなんでもないよな。


hoge の fuga で this がどうとか、こんなの人に聞かれたら頭がおかしいと思われるか、


その逆かだ。



Hoge = function(){
   this.something='lalala..'
}

Hoge.prototype = {
fuga: function() {
alert(this.something);
}
}

var hoge = new Hoge();
setTimeout(function(){hoge.fuga.call(hoge);}, 100);

test はしてないけど、こういうことだ。



チームで仕事するとスキルアップが断然早い。



javascript やる上で、 call と apply は第一関門だと思う。


漫画とかで頭の上で電球がピカッってなるやつ、


ほんとそんな感じだったよ!


うそだけどね。本当はじわじわきた。


染みが広がっていって、あ、世界地図か!みたいなイメージ。



第二関門は、イベントドリヴンへの理解だな。


(個人差があります)




2007/02/10追記


この方法には問題がありました。


http://d.hatena.ne.jp/am11op/20070209/1171039704



JavaScript Template なる javascript のテンプレートエンジンを使ってみた。


あまりにもそのまんまなネーミングである。


ダウンロード、使い方は下記リンクに。


http://trimpath.com/project/wiki/JavaScriptTemplates


英語だけど、簡単だから僕でもわかったよ!



でも日本語にしてくれてる親切な方もいます!


http://d.hatena.ne.jp/shogo4405/20060928/1159422730


http://d.hatena.ne.jp/m-hiyama/20051201/1133396794



使い方は簡単で、


ライブラリをインクルードして、こんな感じにするだけ。



var template = "Hello ${name}!";
var data = {name: 'somebody'}

var myTemplateObj = TrimPath.parseTemplate(template);
var result = myTemplateObj.process(data);

alert(result); //出力結果は「Hello somebody!」


で、javascript からHTML弄る時に、



  • チマチマ弄るならDOM操作

  • ダイナミックに弄るなら innerHTML で(ただし、HTML の組み立てはローカル変数内で行うこと)


と相場は決まってますが、


決まってるらしいですが、


決まってるとどこかで聞いた覚えがありますが、


ダイナミックに弄る場合は HTML テンプレートによる出力で


innerHTML を入れ替えるのがすっきりしてよさそうです。


コードと HTML も分離しやすいしね。



で、これを使ってこんなサンプル作ってみました。


JSTを使ってHTML中に直接テンプレートを書くテスト


こんなソースです。



<html>
<head>
<script type='text/javascript' src='../js/template.js'></script>
<script type='text/javascript'>

</script>
</head>
<body>

<div id='contents'>

${say} World!

</div>

</body>
</html>


ソース中に直接テンプレートを記述しちゃって、


それを getElementByID により取得、


さらに JST かませた結果を差し替えてます。



これによるメリットは



  • HTML を分離できる

  • javascript から出力されるものがそのままそこにあるので、わかりやすい (てことは、意欲的なデザイナさんとの共同作業が可能!)

  • javascript で改行コード挟んだらエラー出るのとか気にしなくてよい


片や、デメリットは



  • javascript をオフにされると、トホホなことに

  • SEO って知ってる?とか意地悪言われるかも



結構強力なデメリットがありますが、


まあ SEO 気にしなくてよいようなページではやってみる価値あるかも。




factory メソッドが好きで、よく使う。


なんで好きかって言うと、if 文を減らせるからだ。


if 文をいかに減らすかで、コードの可読性はだいぶ違ってくると信じてる。


phpでもよく使ってた。



けど、今まで javascript で factory メソッドにお目にかかったことが無い。


フレームワーク的なもののソースをいろいろ見てきたけど


(流し読みとか、エディタで開いたことがあるもカウント)


(いや、ほんとのこというと、そんなにいろいろ見てません。嘘つきました。)、


factory メソッドを使ってるものを寡聞にして知らない。


「寡聞にして知らない」って言葉を使ってみたかった。


ちょっと前まで、「寡聞にして」を全く逆の意味と寡聞にして思ってた。だから、なんて鼻にかかる言葉だと思って使う機会がなかったのだ。



まあ、なんせなんでかなーと思って、試しに使ってみた。


正確に言うと、なんでかなー、便利なのになーとか、


実はみんな知らないんじゃねえのとか、


あれ、俺間違ってんのかなとか、


なんか俺やっぱついていけてない?とか、いろいろ思って使ってみた。



いや、違うわ。


何も考えずに普通に使ってみた後で


いろいろ思うことがあった。



ただ、その時本当に何も考えてなかったかというと(略



例えば javascript で気になる if 文といえば、


やっぱり クロスブラウザ対策だよね。ってことで、


こんな感じでやってみた。



hoge = {}

hoge.factory = function() {
var ua = navigator.userAgent.toLowerCase(),
isOpera = (ua.indexOf('opera') > -1),
isSafari = (ua.indexOf('safari') > -1),
isGecko = (!isOpera && !isSafari && ua.indexOf('gecko') > -1),
isIE = (!isOpera && ua.indexOf('msie') > -1);

if (isIE) {
return new hoge.IE();
} else if (isSafari) {
return new hoge.Safari();
} else {
return new hoge.Gecko();
}
}


で、思ったのは、


これってハードコーディングだよね。ってことです。



ブラウザごとにクラス作ってるけど、


例えば IE6.0 と IE7.0 でも挙動はぜんぜん違うわけで。


バージョンごとにクラス作ってくつもりかよ、っていう。



javascript におけるクロスブラウザ対策では


「関係する機能が使えるか」で処理を分岐させることが多いぽいですね。


attachEvent が使えるか、とか、addEventListener が使えるか、みたいな。



ただし、ブラウザによって判別する他ないような場合もあるわけで。


そういう時には factory メソッドも悪くないんじゃないかと思った。


例えば、ブラウザの「戻る」対策とか。



というわけで、クロスブラウザ対策に factory メソッドが使えるかも。


と思ったけど、実際は微妙だったって結論なわけですが、


factory メソッドの用途はクラスブラウザ対策以外にも全然あるので、表題は間違い。




と思いきや、


prototype.js には bind なる便利メソッドがあるぽい。


その中身はというと、



Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}


なるほど!


やっぱ、 apply が正解か。


ほんとはあんまりわかってないけどね!




で、javascript ちゃんとやりだして、


最初に躓いたっていうか、イマイチ腑に落ちなかったのが


いつの間にか this がどこかいってしまう問題。


多分、初歩的な問題なんだろうけど。



XMLHTTPRequest で callback 指定する時とか、


setTimeout とかの時に、クラスのメソッドを参照渡しすると、


そのクラスの属性が参照できなくなってしまう件である。



function hoge() {
    this.test = 'hello';
}
hoge.prototype = {
    fuga: function() {
      alert(this.test);
    }
}
var hoge_ = new hoge;

//この実行結果は undefined になる。ハズ。
setTimeout(hoge.request, 100);

まあ首をかしげながら、変数をグローバルにしたり、


callback に渡すのはメソッドじゃなくて関数にしたり、


挙句の果てにその関数の中でクラスをもう一度インスタンス化したりして


解決(なかったことに)してたんだけど、



今から思えばまさに迷走である。



こんな感じでやってた。テストしてないので、動くかどうかは知らんです。


動いてもしょうがないし。



var g={}

function hoge() {
g.param='global';
g.text = '';
}

function hoge.prototype = {
request: function() {
var myAjax = new Ajax.Request(
url,
{
method: 'get',
parameters: pars,
onComplete: getResponse
});

},

display: function() {
alert(g.param + g.text);
}
}

function getResponse(o) {
var ins = new hoge();
ins.text = o.responseText;
ins.display();
}

function init() {
var ins = new hoge();
ins.request();
}

Event.observe(window, 'load', init, false);

オブジェクト指向に片思いしてる自分としては、


クラスはそれなりに意味のあるものにしたい。


だから、問い合わせ→データ取得→出力を何とか


一つのクラス内で行いたくって四苦八苦してるのである。


あと、グローバルもなるべく汚したくないしね。



まあ、これで何となく動いてるし、まいっか。


検索してもパキッと出てこないし。


みんなどうしてんだろうとも思うんたけど、


近くの人に聞いてみても、


「this がどっかいっちゃうんですよね~」


「うにゃむにゃむにゃ」って答えだったので、(十中八苦、聞き方が悪い)


放置してた。



これが最近になってなんとなく解決した。


参照を渡せるのは、 php でいうところの static なメソッドのみなんだ。きっと。



だから、 XMLHTTPRequest で callback 指定する時とか、


setTimeout とかの時に this がなくなっちゃうのだ。



これを解決するには、インスタンスをグローバル変数にしてしまえばよい。ハズ。


で、参照渡しされるメソッド内でのみ、そのグローバル変数のインスタンスを使用すればよい。ハズ。



相変わらず、テストはしてないす。



var g={}
function hoge() {
this.param='global';
this.text = '';
}

function hoge.prototype = {
request: function() {
var myAjax = new Ajax.Request(
url,
{
method: 'get',
parameters: pars,
onComplete: g.hoge.getResponse
});

},

display: function() {
alert(this.param + this.text);
},

getResponse: function(o) {
g.hoge.text = o.responseText;
g.hoge.display();
}

}


function init() {
g.hoge = new hoge();
g.hoge.request();
}

Event.observe(window, 'load', init, false);

apply っていう関数使えば何とかなるんじゃないかとも思ったけど、


多分、これ違う。



つーことで体で覚えた javascript の作法でした。


ほんとの正解はどんななんだろう。