2005-09-29

デザインを変えました

サイドバーを無くして文字を大きくしました。あと検索ボックスがど真ん中、ってスタイルを流行らせたい。
検索ルーチンを大幅に変更しています。まあ使いやすいかどうかは別として、いろいろ凝ったことをやってみました。
タイマー使ってスレッド処理っぽくしています。エントリの件数が1万件とかになってもブラウザを固めずに検索ができます。1万件とかならないのであんまり違いはでないんだけど。

副産物で色々と出来上がったのだけど結局ほとんど使ってない。
ソース見たい人はここからどうぞ。全然整理できてないけど。
http://la.ma.la/js/

色々変なことをやっているので、そのうち個別に解説を書くかも。

2005-09-22

三項演算子の正しい書き方ってあるのだろうか

最近は、三項演算子を多用しているのだけれど、やっぱり人が使ってるのを見ると気持ち悪い。

http://d.hatena.ne.jp/brazil/20050921/1127314004
Test.Builder.globalScope = typeof JSAN != 'undefined'
  ? JSAN.globalScope
  :  typeof window != 'undefined'
    ? window
    : typeof _global != 'undefined'
      ? _global
      : null;

これは解読するのにやたら時間がかかる気がする。コロンの後が値なのか条件なのかわからないからだ。

俺ならこう書く。
Test.Builder.globalScope = 
 (typeof JSAN    != 'undefined') ? JSAN.globalScope :
 (typeof window  != 'undefined') ? window :
 (typeof _global != 'undefined') ? _global :
 null;

三項演算子を使うときは、条件はどんなに短くても必ずカッコで囲って、先頭に持ってくるようにしている。
条件をカッコで囲うだけでも、かなり読みやすくなる気がする。

このコードはこういう風に読み替えることができる。
if (typeof JSAN != 'undefined') { JSAN.globalScope }
else if (typeof window != 'undefined') { window }
else if (typeof _global != 'undefined') { _global }
else { null };

あるいはこういう風に。
switch(true){
 case (typeof JSAN != 'undefined')    : JSAN.globalScope 
 case (typeof window != 'undefined')  : window 
 case (typeof _global != 'undefined') : _global 
 default : null ;
}

脳内補完されるコードと近いので、動作が想像しやすいんじゃないかと思う。
実際はif else使う場合はTest.Builder.globalScope = windowみたいに全部代入文にしないといけないしswitch文の場合はbreakも必要になるので、三項演算子を使ったほうが、はるかに短く書ける。
しかし、三項演算子でif elseを全部置き換えられるかというとそうでもなくて、returnとかcontinueとかbreakとか、値を返さない制御文の類を使うことはできない。
あと、イコールの後に改行はOKなんだけどreturnの後に改行するとJavaScriptはセミコロンが省略されてると見なしてうまくいかない。

2005-09-17

GoogleInstantSearchを作ってみた

Yahoo! Instant Searchにインスパイヤされて作ってみた。だいたい1時間ぐらい。

Greasemonkeyスクリプト
http://la.ma.la/misc/userjs/GoogleInstantSearch.user.js

動作対象をgoogleドメインの/で終わるページに設定しているので、動かない場合はincluded pagesをホームページに合わせて設定しなおしてください。

こんな感じに動く。
http://la.ma.la/misc/img/gis.png

最初にFeeling luckyで検索をかけて、locationヘッダで返ってくるURLで再度検索をかけて詳細情報を出しています。検索のタイミングはキーアップから500ミリ秒後。Ctrl+Enterでヒットしたサイトに移動。

あとなんか、Greasemonkeyがバージョンアップして日本語が通るようになってたので、日本語表示にしておいた。

2005-09-12

Firefox1.5でサポートされる新しいArrayのメソッド

について。

http://nanto.asablo.jp/blog/2005/09/04/62939
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array#Methods

ループ処理を便利にするメソッド


Array#forEach
Array#map
Array#filter
Array#every
Array#some

引数として、callback,thisObjを受け取る。配列の各要素に対してcallback関数を実行する。
callback関数はelement, index, arrayを受け取る。value,key,self。値、添え字、配列自身。

Array#forEach
配列の各要素に対してcallback関数を実行する
Array#map
callback関数の返値の配列を返す
Array#filter
callback関数の実行結果が真になった要素のみ集めた配列を返す
Array#every
callback関数の実行結果が全て真の場合にtrueを返す
Array#some
callback関数の実行結果が一つでも真の場合trueを返す

出現位置を探すメソッド


StringのindexOfと同じ要領。searchElementとfromIndexを受け取る。比較は===で行う。

Array#indexOf
searchElementが一番最初に出てくる添え字を返す。fromIndexで開始位置を変更できる。
Array#lastIndexOf
searchElementが一番最後に出てくる添え字を返す。fromIndexで開始位置を変更できる。

lastIndexOfの挙動が少しややこしくてfromIndexをプラスで指定して左端まで来ると右端で止まる。マイナスで指定する場合は左端で止まる。

他のブラウザで使うためのコード


解説がわかりづらいので、わかる人はコードで読んだほうが早いような気がする。
Firefox1.5をインストールして、挙動が同じになるのを確認してみたけど、callback関数で配列を破壊したときにどうなるのかとか、細かいチェックはしてない。一応、これであってるとは思う。
filterやeveryの条件は0とかnullとかundefinedでなければOK。return 1でも良い。
自分でmapとかeachとか作ってやってたけど、これに合わせてcallback関数の類はvalue,key,selfの順に受け取るような設計にしときたいところ。

Array.prototype.forEach = function(callback,thisObject){
    for(var i=0,len=this.length;i<len;i++)
        callback.call(thisObject,this[i],i,this)
}
Array.prototype.map = function(callback,thisObject){
    for(var i=0,res=[],len=this.length;i<len;i++)
        res[i] = callback.call(thisObject,this[i],i,this);
    return res
}
Array.prototype.filter = function(callback,thisObject){
    for(var i=0,res=[],len=this.length;i<len;i++)
        callback.call(thisObject,this[i],i,this) && res.push(this[i]);
    return res
}
Array.prototype.indexOf = function(searchElement,fromIndex){
    var i = fromIndex<0 ? this.length+fromIndex : fromIndex || 0;
    for(;i<this.length;i++)
        if(searchElement === this[i]) return i;
    return -1
}
Array.prototype.lastIndexOf = function(searchElement,fromIndex){
    var max = this.length-1;
    var i = fromIndex < 0 ?    Math.max(max+1 + fromIndex,0):
        fromIndex > max ? max : max-(fromIndex||0) || max;
    for(;i>=0;i--)
        if(searchElement === this[i]) return i;
    return -1
}
Array.prototype.every = function(callback,thisObject){
    for(var i=0,len=this.length;i<len;i++)
        if(!callback.call(thisObject,this[i],i,this)) return false;
    return true
}
Array.prototype.some = function(callback,thisObject){
    for(var i=0,len=this.length;i<len;i++)
        if(callback.call(thisObject,this[i],i,this)) return true;
    return false
}


ところでさいきん、{}を省略したりむやみに三項演算子を使ったりifの代わりに&&使ったりする病気になった。

2005-09-03

FeedBurnerを使うようにした

転送量とサーバー負荷が大変なことになっているのでRSSフィードをFeedBurnerに移転しました。ドメイン全体のトラフィックの90%ぐらいがRSSへのアクセスで、1日700MBとか。7月には400MBぐらいだったんだけど。

301 Moved Permanently(恒久的な移転)を吐いているので、Bloglines使ってる人は、特に何にもしなくても平気です。

他のリーダーでも、ほっといてもリダイレクトするので問題は無いのだけど、毎回リダイレクトすることになるので、RSSバーとかFirefoxのLivebookmarkを使ってる人は、登録しなおしてくれるとありがたいかも。というか、この際Bloglinesを使ってくれると一番ありがたい。

今なんだかんだで500フィードぐらい購読してるのだけどBloglinesじゃなかったら読める気がしない。

----
9/3

Bloglinesは三日ぐらい様子を見て移転するらしい。
あとなんかはてなRSSが巡回に来なくなった。
どうしてくれよう。

IEでXMLHttpRequestを使えるようにする

もうあんまり需要無いような気もするけど。
IEで「new XMLHttpRequest」と書けるようにするラッパを書いてみた。

XMLHttpRequest for IE
http://la.ma.la/misc/js/ie_xmlhttp.html

結構前にIE7(JavaScriptの方)の人が似たようなのを作ってたのですが、
http://dean.edwards.name/weblog/2004/11/ie7-xml-extras/

ふつうに使う分にはこれでも問題はないです。

if(typeof ActiveXObject == "function" && typeof XMLHttpRequest == "undefined"){
    XMLHttpRequest = function(){
        return new ActiveXObject("Microsoft.XMLHTTP")
    }
}


IEでXMLHttpRequestが未定義だったら、XMLHTTPオブジェクトを返すコンストラクタを定義してやる。
これだけでいい。ただ、これだとXMLHttpRequest.prototypeをいじったりすることができない。
IEのActiveXObjectっていうのはちょっと特殊で、メソッドなんかがJavaScriptのfunctionではない。

var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
alert(typeof xmlhttp.open); // unknown


こうなる。xmlhttp.openはfunctionではなくて謎オブジェクト。
FirefoxやOperaのXMLHttpRequest#openは、ちゃんとfunctionになっているのだが、
IEの場合は関数ではないので、関数型言語としてのJavaScriptの機能が使えない。

----

XMLHttpRequest for IEは何なのかというと、
IEでもXMLHttpRequestのメソッドをfunctionオブジェクトとして扱うことができるようになるラッパです。
あとついでにonloadイベントハンドラを定義してあるので記述が楽になります。

functionalでアスペクト指向なコードを書く際に、役に立ちますが、
functionalでアスペクト指向なコードを書かない場合は書き方が共通になって嬉しい程度で、
その辺はすでにprototype.jsとかあるので大してメリットはないと思われる。

ものすごくシンプルな例はこんな感じ。

var XP = XMLHttpRequest.prototype;
XP.open_old = XP.open;
XP.open = function(){
    alert("open called!");
    return this.open_old.apply(this,arguments)
}


XMLHttpRequest.prototypeを書き換えてやることで、通信をトレースできるようにしたり、機能を拡張したり制限を加えたりすることができるようになる。もちろんクロスブラウザで動く(safariは知らないけど)。

例えば、通信するときには必ず前回のリクエストから1秒のウェイトを入れるようにする、とか、
デバッグ用に通信状態をステータスバーに流すとか、
他ドメインのURLを開こうとした場合はプロキシCGI経由のリクエストにするとか、
UserAgentを強制的に書き換えるとか、キャッシュが利かないようにユニークなURLにするとか。

全般にわたる振る舞いを効率的に記述することができるようになる。
----

工夫したのはこのあたり。

var methods = "open,abort,send,setRequestHeader,getResponseHeader,getAllResponseHeaders".split(",");
var make_method = function(name){
    XMLHttpRequest.prototype[name] = function(){
        var params = new Array(arguments.length);
        for(var i=0;i<params.length;i++) params[i] = "_"+i;
        return Function(
            params.join(","),
            ["return this.__request__.",name,"(",params.join(","),")"].join("")
        ).apply(this,arguments);
    }
};
for(var i=0;i<methods.length;i++) make_method(methods[i]);

本来のXMLHTTPオブジェクトをthis.__request__に保存していて、
引数の数に応じて関数を動的に生成して、本来のXMLHTTPオブジェクトに処理を丸投げする。

this.open("GET",url)
→(function(_1,_2){this.__request__.open(_1,_2)}).apply(this,arguments)

this.open("GET",url,async)
→(function(_1,_2,_3){this.__request__.open(_1,_2,_3)}).apply(this,arguments)

this.open("GET",url,async,user,pass)
→(function(_1,_2,_3,_4,_5){this.__request__.open(_1,_2,_3,_4,_5)}).apply(this,arguments)

こんな具合に変換される。

ふつうにラッパーを書くときは、
function wrapper(){
    return original.apply(this,arguments)
}

こんな具合にやるんだけど、オリジナルのxmlhttp.openは上記のとおり、そもそも関数ではないのでxmlhttp.open.applyが存在しない。
eval使わないと無理かな、とも思ったんだけどFunctionコンストラクタ使うときれいに書けた。

Functionコンストラクタは文字列から関数を生成する。
add = new Function("a","b","return a+b");
そして、こう書いてもOK。
add = new Function("a,b","return a+b");

これはJavaScriptの方言ではなくてちゃんとECMA準拠。
http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/15-3_Function_Objects.html#section-15.3.2.1

まあ、あんまり使わないと思うんだけど。