Javascriptクイズ(中級者向け):無名関数と実行効率の話

 Javascriptを使い始めたばかりの太郎君に、上司の花子さんから「スタイルシート用のスタイル名をDOMからアクセス可能なプロパティ名に変更するJavascriptのライブラリを作って欲しい」という課題が与えられました。何のことか分からずにポカンとしている太郎君に、花子さんは「"font-style" を"fontStyle"に変更する関数を作ればいいのよ」と言い残して立ち去ってしまいます。

 O'ReillyのJavascript本としばらく格闘した太郎君は、やっとライブラリを完成させます。

function capitalize(str)
{
    return str.charAt(1).toUpperCase();
}

function style2prop(str)
{
    return str.replace(/-[a-z]/g, capitalize);
}

 "font-style"が"fontStyle"にちゃんと変更されることを確認した太郎君は、コードレビューのために先輩の次郎君のところに行きます。すると次郎君は、「良くできたね」と褒めてはくれるのですが、「でも、これじゃあ"capitalize"っていう内部関数がライブラリの外から見えてしまうよね。こういうときは、無名関数を使って隠蔽すべきなんだよ」と言いながら、サラサラと書き直します。

function style2prop(str)
{
    return str.replace(/-[a-z]/g, function(str) {
        return str.charAt(1).toUpperCase();
    });
}

「無名関数ってこういうところに使うんですね」と太郎君が感心していると、そこにちょうど通りかかったのが、Javascriptの実行効率に関してものすごくうるさいことで知られる三郎君。「なになに?見せて」という三郎君に、コードを見せると、

「これ、実行効率上少しオーバーヘッドがあるよね。内部関数を隠蔽したいならもっと良い方法があるよ」と言います。

 さて、ここで問題です。三郎君が言っている「実行効率上のオーバーヘッド」とはいったいなんでしょう?そして、三郎君が言う「もっと良い方法」とはどんなものでしょう?


iAnime.js でWicketのホームページを作ってみた

 他のアニメーション・ライブラリについて調べていたら出会ったのが、「Javascript animation libraries compared」。Wiketのホームページのアニメーションを、Yahoo! Animation と animator.js の両方を使って実装して比較している。ちょうど良い機会なので、iAnime.jsを使って同じものを作ってみたのがこれ。

Wiket home page using iAnime.js

 ソースコードはこんな感じ。このJSONベースの言語のおかげで、他の二つよりもずっと簡単だ。

    anime.addSequence([
                [{ id:'top', effect:'bounce', y:top, duration:2000 }],
                { id:'logo', effect:'easeout', duration:2000,
                  x:300+(iBrowse.getWindowWidth()/2-120) },
                [{id:'apache', effect:'fadein', duration:1500}],
                [{id:'wicket', effect:'fadein', duration:1500}],
                [{id:'introduction', effect:'fadein', duration:2000}],
                {id:'download', effect:'fadein', duration:2000}
            ]);

 ちなみに、このアニメーション用の言語の名前だが、JAASL("ジャーズル"と発音)というのはどうだろう。JSON Asynchronous Animation Sequencing Languageの略だ。


Javascriptの黒魔術=クロージャを宮沢賢治風に説明してみる

 

先日のエントリーで、「Javascriptの黒魔術」と呼んだのはクロージャのこと。関数呼び出しの際に一時的に作られただけのはずのローカル変数が、ある条件が整うとその関数の実行が終わった後もゾンビのように生き延びて参照が可能、というのがJavascriptのクロージャだ。

 これを宮沢賢治風に書いてみるとこんな感じになる。

 ある町でふらりと一人で立ち寄ったカラオケ・スナックは、カウンター席しかないとても小さな店だが、客のノリが良くてとても気持ちよく歌えた。おかげでマイクが離せなくなってしまった私に、帰り際にマスターが「そんなに気に入ったのなら、そのマイク持って返ってもいいですよ」と言う。

 言われるままに持って返ったマイクは、しばらく家のテレビの横にだらしなく置いてあったのだが、ある晩に歌番組を見ていると私がカラオケで十八番にしている曲が流れるものだから、おもわずそのマイクを手に取って歌ってみた。すると驚いたことに、あのスナックにいた客とマスターがどこからともなく現れ、あの晩のように声援を送ってくれるではないか。

 驚く私に、マスターがウインクして言う。「これがJavascriptのクロージャってヤツなんです。気にせず、どんどん歌ってください」。

 やっぱり黒魔術だ。


フィボナッチ関数とJavascriptの黒魔術と

 「最近あなたのブログ、プログラミングの話ばっかりじゃない?」とは妻の指摘。確かにその通りなんだが、これとかこれを読んでしまうと、どうしてもそちらに走りたくなるのが私の性分。ということで、とことん「ギーク街道」に堕ちてみた。

 今回の作品は、iAnime.jsの非同期JSON言語(名前はまだない)を使って非同期に自分を再帰的に呼び出しながらただひたすらにフィボナッチ数列を表示するというプログラム。

function start(k1, k2)
{
    var span = (k2+" ").length*10;
    var obj=document.createElement("span");
    obj.style.position = "absolute";
    obj.style.left = -span+"px";
    obj.style.right = "0px";
    iBrowse.setText(obj, k2);
    document.body.appendChild(obj);
    var delay = 30000 * span / 400;
    anime.addSequence([
        [{ duration:delay, onComplete:function() {start(k2, k1+k2);} }],
        { element:obj, x:400-span, effect:'fadeout', duration:30000 },
        { onComplete:function() { document.body.removeChild(obj); }}
    ]);
}

 パラメータ(k1, k2)が無名関数の引数としてだけ、それも非同期に引き継がれて行くところが、C++/Javaプログラマーたちにとっては黒魔術のように見えるかも知れない。


Flickrスライドショーの作り方

 先日のスライドショーを少し進化させて、Flickrから最新の夕焼けの写真を取り出して順番に表示させる、というものを作ってみた。まずはデモから。

 RSSフィードをFlickrに取りに行く部分にはjQueryを使い、スライドショーにはiAnime.jsを使ったのだが、ライブラリの力に大きく頼っているため、このスライドショー自身のコードはごくわずかである。

 まずは、フィードを取りに行く部分がinit()。クロスドメインでのアクセスのためにproxyを介してFlickrからフィードを非同期通信で取得し、XMLとしてパースして、各<entry>中の<content type="html">タグの中身を含む<div>を生成して<div id="main">にインサートし、インサートしたdivの数をパラメータとしてstart()を呼ぶ、というかなり複雑な作業が、わずか数行で実現できている。

var tmplt="<div class='slide' id='pic{INDEX}' 
                onmouseover='HandleMouseOver(this)'
                onmouseout='HandleMouseOut(this)'>{HTML}</div>";

function init()
{
    jQuery.ajax({
        url: 'http://www.uicentric.com/flickr_proxy.php',
        data: 'url=http://api.flickr.com/services/feeds/photos_public.gne&tags=sunset',
        dataType: 'xml',
        success: function(xml) {
            var all = "";
            var i = 0;
            jQuery("entry", xml).each(function() {
                var html = jQuery("content[type='html']", this).text();
                all += tmplt.replace(/{INDEX}/, i++).replace(/{HTML}/, html);
            });
            $("#main").html(all);
            start(i);
        }
    });
}

 ここまでコンパクトに書けるとすべてが一望できてしまうため作業効率がものすごく上がる。これぞインラインfunctionの威力だ。Cでこんなコードを書いたらいったい何行になるのか考えただけでもぞっとする。ちなみに、HTMLテキストの生成にレギュラー・エクスプレッションを使っているのは読みやすさのため。"<div id='pic'"+id+"'...  の方が効率は良いかも知れないが、このケースでは読みやすさを優先すべきと判断。

 init()でインサートした画像を含んだ<div>を順番に表示させるのは、start()の役目。iAnime.jsのJSONベースのアニメーション記述言語を使っているのだが、ミソは入れ子のアレイで指示しているいままで表示していたもののfadeoutを指示している部分。新しいdivは2秒でfedeinさせつつ、一つ前のものは0.5秒待ってから0.5秒でfadeoutさせてから完全にdisplay:hiddenにする、という意味である。

var anime = new iAnime();

function start(count)
{
    var seq = [];
    var prev = count-1;
    for (var i=0; i<count; i++) {
       seq = seq.concat([
           [
               {duration:500},
               {id:'pic'+prev, effect:'fadeout', duration:500},
               {id:'pic'+prev, effect:'hide'}
           ],
           {id:'pic'+i, effect:'show'},
           {id:'pic'+i, effect:'fadein', duration:2000},
           {duration:4000}
       ]);
       prev = i;
    }
    seq.push({ onComplete:function() {start(count);} });
    anime.addSequence(seq);
}

 残りのスクリプトは、マウスがスライドショーの上に来た時に一時的にアニメーションを止めるという処理。

function HandleMouseOver(obj)
{
    anime.pause(true);
    iBrowse.setOpacity(obj, 1);
    obj.style.border = 'solid 2px #6666ff';
}

function HandleMouseOut(obj)
{
    anime.pause(false);
    obj.style.border = 'solid 2px #eeffff';
}

ianime.js: ベジエ曲線上を動かす機能を追加

 先日の「アニメーション用のメタ言語」のエントリーに対して、「Flash用のfuseに似ている」というコメントをいただいた。調べてみると、並列処理に関する考え方が少し異なっているが、確かに似ている面もある。そのFuseのデモの一つに、ベジエ曲線上にオブジェクトを動かして、その軌跡をフェードアウトで表現するというなかなか見栄えの良いものがあったので、対抗意識を燃やしてianime.jsで実装してみた。

 デモと簡単な解説は英文ブログのエントリーに書いてあるが、ベジエ曲線の機能はこのデモのために新しく追加した機能。作る前はパフォーマンスが少し心配だったが、iPhone上でも問題なく動いているので一安心。

 しかし、こんなことまでがHTML+Javascriptだけで実現できてしまうとは我ながら少々驚いている。ActionScriptと比べてどうも取っ付きにくいと感じていたJavascriptだが、ここまで可能ならばJavascriptでかなりのことをやってしまった方が良いのではないかと思えて来た今日この頃である(iPhoneでも動くし^^)。


iAnime.js をGoogle code上のオープンソース・プロジェクトとして正式スタート

 「オープンソースとして自分の書いたコードを公開する」意味を知るために初めたianime.jsプロジェクト。そろそろ形も整い始めたので、週末を利用してGoogle codeにプロジェクトを立てて本格的なオープン・ソース・プロジェクトとしてスタートすることにした。 

Google code - iAnime.js: ligh-weight javascript engine for PC and iPhone

 これからドキュメントやサンプルの整備など、やることはたくさんあるが、こうやって公の場にプロジェクトを立てることによって自分にプレッシャーを与えるのも良いかも知れない。

 あまり欲張っても長続きしないだろうから、とりあえずはiFreecellを題材に、iAnime.jsを使ったゲーム・プログラミング入門のようなものをシリーズ化してwikiに書いてみようかと思う。

 ちなみに、ライセンス形態としては自由度の高い「MIT license」とすることにした。何にしようか悩んでいた時に、ちょうど良いタイミングで「ライセンス形態はMIT licenseですか」という質問を受けたので、それを「MIT licenseにしてくれるとありがたい」というヒントと解釈して、こうしたしだいである。


アマゾン用「今月のベストセラー」ウィジェットを作ってみた

 先日作ったianime.jsを使ったスライドショー。何か具体的に役に立つものができまいかと作ってみたのが、左に貼付けた「今月のベストセラー」ウィジェット(もしくはブログパーツ)。スライドショーの上にマウスカーソルを持って行くと、そこでアニメーションが一時停止し、クリックするとちゃんとアマゾンのその本のページにアフィリエイトID付きで飛ぶところがミソだ。

 Javascriptはライブラリ化しておいたのでHTMLページをコピーしていただければ、自分独自のウィジェットを作ることは難しくないはず。もし要望があれば、「ジェネレータ」を作っても良いと考えている。

 ちなみに、今月はあまり書評を書かなかったので、売り上げはほとんどが過去のエントリーからのもの。目新しいものといえば「ハーバード流交渉術」ぐらいだ。興味深いのは9位に食い込んだ「.fla」。出版社が倒産してしまい入手が困難になりそうだ、という情報を得た人が駆け込みで買っているのか。


JSONアニメーション言語の並列実行機能について

 一つ前のエントリーで紹介したJavascript上のアニメーション記述言語(格好良い名前募集中^^)。先の例は単に上から順番に実行するだけであったが、それだけでは1度に一つのオブジェクトしか動かすことができず、表現力に乏しい。より複雑なアニメーションを実現するには、複数のオブジェクトに別々の動作を同時にさせることができなければだめだし、記述言語もそれをサポートしていなければならない。

 そこで試しに実装してみたのが、シーケンスの入れ子構造による並行処理の記述。けっこうすごいことができてしまうのだが、実装は意外にあっさりとできてしまったので(Javascriptで数行)自分でも少し驚いている。

 ちなみに、動作A, B, C, D, Eを順番に実行させる場合には、先の例のように、

 [A, B, C, D, E]

と書けば良い。これはすなわち、「Aが終わったらB、Bが終わったらC、...」という意味である。

 ここで、このアニメーションの実行に加えて(つまりメインのシーケンスには手を加えずに)、「Bが終わったらP、Pが終わったらQ」(つまりPとCは同時に実行される)と指示を出したい場合には、

 [A, B, [P, Q], C, D, E]

と書けば良いのだ。具体的な例で言えば、

 [
        { duration:3000 },
        { id:'pic4', effect:'fadeout', duration:3000 },
        [
            { id:'text1', effect:'settext', text:'Hello World', duration:2000 },
            { id:'text1', effect:'fadeout', duration:500 }
        ],

        { duration:3000 },
        { id:'pic3', effect:'fadeout', duration:3000 },
        { duration:3000 },
        { id:'pic2', effect:'fadeout', duration:3000 },
        ...
    ]

と書くことになる。太字の部分が[P,Q]に相当する部分だ。

 こんな説明だと、何の話だかピンと来ないだろうから、実際のサンプルを貼付けておく。一つ前のサンプルと全く同じシーケンスで写真を順番に表示しつつ、それぞれにキャプションをつけるというシーケンスを入れ子構造にして挟み込んでいる点がミソである。

 

JSONでアニメーション用のメタ言語を作ってみた

 ianime.jsもようやく安定して動き出したので、スライドショーを作ってみようと思ったのだが、通常のjavascriptのイベント処理を使って作ろうとすると、(1)最初のアニメーションの動作を指定し、(2)そのアニメーションの終了イベントを受けて次の指示を出し、...と、ものすごいスパゲッティ・コードを書かねばならなくなる。

 それがどうしても耐えられなかったので、色々と試行錯誤をしているうちにたどり着いたのが、JSONを使ったアニメーション専用のメタ言語である。下の例の太字の部分がそれ。

function start()
{
    anime.addSequence([
        { duration:3000 },
        { id:'pic4', effect:'fadeout', duration:3000 },
        { duration:3000 },
        { id:'pic3', effect:'fadeout', duration:3000 },
        { duration:3000 },
        { id:'pic2', effect:'fadeout', duration:3000 },
        { duration:3000 },
        { id:'pic4', effect:'fadein', duration:3000 },
        { id:'pic3', effect:'fadein' },
        { id:'pic2', effect:'fadein' },
        { onComplete:start }

    ]);
}

 上から順番に説明すると、
  • 何もせずに3秒間待つ(一番上にあるpic4が表示される)
  • 3秒間かけてpic4をフェードアウトする(pic3が見えてくる)
  • 何もせずに3秒間待つ
  • 3秒間かけてpic3をフェードアウトする(pic2が見えてくる)
  • 何もせずに3秒間待つ
  • 3秒間かけてpic2をフェードアウトする(pic1が見えてくる)
  • 何もせずに3秒間待つ
  • 3秒間かけてpic4をフェードインする
  • フェードアウトしてあったpic3を元に戻しておく(pic4に隠れて見えない)
  • フェードアウトしてあったpic2を元に戻しておく(pic4に隠れて見えない)
  • 最初に戻る

となる。直感的にアニメーションのシーケンスが記述できるので、私自身はとても気に入っているのだがいかがだろう。

 上のサンプルをIFRAMEの形で下に貼付けておいたので、興味のある人はソースコードをコピーして、自分なりのスライドショーを作って遊んでみていただきたい。フェードイン・アウトだけでなく画像を移動させることも可能だし、テキストを含むdivやspanをフェードイン・アウトさせたり、ということも自由にできる。

 ちなみに、ianime.jsは若干進化してv0.24。v0.24に追加した機能のドキュメントはまだ書いていないが、このサンプルを見ていただければだいたいのことは分かっていただけると思う。