2009年02月24日 04:30 [Edit]

javascript - ソースを見せてかつ動かすための3つのtips

livedoor Blogを私が愛用しつづけている理由のひとつが、JavaScriptを受け付けること。

おかげでかなりのentriesが溜まりましたが、それだけにで実行用のソースと表示用のソースを用意するのが人一倍おっくうに感じられます。そんなわけで、どうやれば怠慢をもっと発揮できるかをまとめてみました。


0. DOMにIDをふらずにデモる

こんな感じのデモがあったとしましょう。「404 Blog Not Found:javascript - Array#sortがオレquicksortより遅い!?」にあるものを書き直したものです。

# of Items:

よくあるのは、inputタグやpreタグにIDをふって、document.getElementById('foobar')でアクセスするというもの。Prototype.jsやjQueryを使っていれば$('foobar')と短くはなりますが、HTMLを書く方の手間は減りません。

しかしこのように書くことで、IDは不要になります。

# of Items:
<input type="text" value="10000" size="8" maxlength="8"
><input type="submit" value="start" onclick="run_demo(this)"
><pre></pre>

で、run_demoの中身ですが、以下のようになっています。

run_demo = function(that){
    var val = that.previousSibling.value;
    var pre = that.nextSibling;
    pre.innerHTML = '';
    var seed = random_array(val);
    (function(o){
        for (var k in o) (function(l, f){
            window.setTimeout(function(){
                pre.innerHTML += l + ':\t' + timeit(f, seed) + 'ms' + '<br>';
            }, 0)
        })(k, o[k]);
    })({
        builtin:builtinsort,
        quick_iter:quicksort_i,
        quick_recur:quicksort_r
    });
};

souce_code_above.replace(/that/g,'this')すれば、そのままボタンのonclickの中にぶちまけられます。ここでは最後のtipのために、あえて関数にして外だししています。

1. PREタグの中身をクリックで実行する

こんな感じのものです。

alert('Hello, JavaScript');

これは、以下のとおりに実現出来ます。

<pre>
alert('Hello, JavaScript');
</pre><input type="submit" value="Run" onclick="
    try{
        var e = this.previousSibling;
        eval( e.innerText || e.textContent );     
    }catch(e){
        alert(e);
    }
">

preタグとinputタグの間に隙間がないのは、tip#0と同様の理由です。

preではなく、textareaやinputなら、さらにシンプルになります。

こんな具合に。

<textarea>
alert('Hello, JavaScript');
</textarea><input type="submit" value="Run" onclick="
    try{
        eval( this.previousSibling.value );     
    }catch(e){
        alert(e);
    }
">

3.PREタグの中身を、ロード時に評価する

上の二つは実は今までも愛用してきたのですが、もう一つのtipを見つけたので。

本題に移る前に、まずは上のデモで使われた関数の説明などを。

timeit = function(){
    var elapsed = (new Date).getTime();
    var args = Array.apply(null, arguments);
    args.shift().apply(null, args);
    elapsed =  (new Date).getTime() - elapsed;
    return elapsed;
}

見てのとおり、実行時間を計測します。timeit(func, arg)という風に使います。 args.shift().apply(null, args)が結構気に入ってます。

random_array = function(n){
    var ret = [0];
    while(--n) ret[n] = n; // sorted array
    for (n = ret.length-1; n; --n) { // Fisher-Yates Suffle
        var m = Math.floor(Math.random() * (n + 1));
        if (n == m) continue;
        var tmp = ret[n]; ret[n] = ret[m]; ret[m] = tmp;
    }
    return ret;
};

n個の要素が入ったランダムな配列を作ります。

middle = function(h, t){ 
    return h + ((t - h) >>> 1)
};
cmp_number = function(a, b){
    return a - b;
}

ソートの実装でよく用いられる関数です。

builtinsort = function(ary, cmp){
    return ary.slice().sort(cmp || cmp_number);
};

builtinのArray.prototype.sort()を、非破壊的に実行します。ary.slice()で元の配列のコピーを作っています。

quicksort_r = function(ary, cmp){
    var ret = ary.slice();
    if (! cmp ) cmp = cmp_number;
    return (function (head, tail) {
        var pivot = ret[middle(head, tail)];
        var i = head - 1;
        var j = tail + 1;
        while (1){
            while (cmp(ret[++i], pivot) < 0);
            while (cmp(ret[--j], pivot) > 0);
            if (i >= j) break;
            var tmp = ret[i]; ret[i] = ret[j]; ret[j] = tmp;
        }
        if (head < i - 1) arguments.callee(head, i - 1);
        if (j + 1 < tail) arguments.callee(j + 1, tail);
        return ret;
    })(0, ary.length - 1);
}

quicksortの実装です。クロージャーのおかげで、再帰呼び出しに必要な引数の数が二つで済んでいます。さらにarguments.calleeのおかげで、その再帰呼び出しすら無名です。

quicksort_i = function(ary, cmp){
    var ret = ary.slice();
    if (! cmp ) cmp = cmp_number;
    var stack = [ 0, ary.length - 1 ];
    while(stack.length){
        var tail = stack.pop();
        var head = stack.pop();
        var pivot = ret[middle(head, tail)];
        var i = head - 1;
        var j = tail + 1;
        while (1){
            while (cmp(ret[++i], pivot) < 0);
            while (cmp(ret[--j], pivot) > 0);
            if (i >= j) break;
            var tmp = ret[i]; ret[i] = ret[j]; ret[j] = tmp;
        }
        if (head < i - 1) { stack.push(head); stack.push(i - 1); }
        if (j + 1 < tail) { stack.push(j + 1); stack.push(tail); }
    }
    return ret;
}

こちらは非再帰版。再帰関数をどうやったら非再帰化するのかをわかりやすく書いてみました。

で、やっとここからが本題。実は、ここまでにscriptタグは一切登場していません。各関数の定義はpreタグの中にのみ存在します。ただし、全てのpreタグをscriptタグにはしたくないので、class="evalonload"となっているもののみを評価することにします。

その結果が、以下です。

(function(root, className){
     var walkDOM = function(node, f){
        f(node);
        var kids = node.childNodes;
        for (var i = 0, l = kids.length; i < l; i++) arguments.callee(kids[i], f)
    };
    walkDOM(root, function(node){
        if (node.nodeType  !== 1) return;
        if (node.nodeName.toLowerCase() !== 'pre') return;
        if (node.className !== className) return;
        node.className = 'prettyprint'; // for google-code-prettify
        try{
            eval(node.innerText || node.textContent);
        }catch(e){
            alert(e + "\n" + (node.innerText || node.textContent));
        }
    });
})(document.body, 'evalonload');

実にシンプルなものです。walkDOMは無名化できちゃいますが、よく使う機能なので、そこだけ抜き出せるようにしてあります。ここだけをscriptタグとして記事の最後にコピペすれば、あとは普通にソースを貼付けて行くだけで「見えてかつ動く」ページが出来上がるというわけです。

なお、今回のtipsは Firefox3, IE7, Safari3, Opera9, Chrome1 でテストしてあります。

Enjoy!

Dan the Lazy JavaScripter


この記事へのトラックバックURL

この記事へのトラックバック
WordPressでJavascriptのソースを表示し、実行する場合に手軽な方法はないかと検索していて、小飼弾さんの404 Blog Not Found:javascript – ソースを見せてかつ動かすための3つのtipsにたどり着いた。なるほど、1番の方法はシンプルで分かりやすい。PREタグの直後に実行ボ
WP-JS-Run プラグイン【karak】at 2013年01月11日 09:57
てなわけで早速アップデートしました。 Mozilla Japan ブログ - "Shiretoko ショック" が世界中を駆け巡る!Firefox 3.5 - コードネーム「Shiretoko」 が、日本時間 7 月1 日 0:00 AM リリースされます。そのリリースを記念して、オンライン上で、世界中のみんなと一....
News - #fx35 - 早速Firefox 3.5にしました【404 Blog Not Found】at 2009年07月01日 01:12
弾さんのjavascript - ソースを見せてかつ動かすための3つのtipsという記事をよんで「JavaScriptの勉強blogやるならlivedoor Blogっていいかも!」と思いさっそくはじめてみました。 とりあえず、弾さん作の実行用のソースと表示用のソースを用意する為のコード(要するに....
JavaScript blog をはじめるにあたって【JavaScript 備忘録】at 2009年02月25日 02:10
dankogaiさんのBlogからソースをコピペして、WebServerに仕立てたiPhoneにアップしてみました。 URLはここ。誰か人柱になって、iPhoneから動作確認してくれるとありがたいんですが。 iPhoneをサーバーとして起動しているときは、Safariなど他のアプリが動かせないので自分....
おいらのiPhoneがWebServerになりました、その2【ヒトカゲの独り言】at 2009年02月25日 00:28
この記事へのコメント
function q(head, tail)

呼び出しが無名なのにqっていう名前をつけているのにはどういう意味があるのだろう…?
Posted by U D at 2009年02月24日 15:08
// 各位、
// oops. fixed.
try{ eval(my.entry) }catch(e){ your.comment }
Posted by at 2009年02月24日 13:16
eval( e.textContent || e.innerText );

ですね。:-)
Posted by mattn at 2009年02月24日 09:20
PREタグの中身をクリックで実行するのソースが
e.textContent || e.textContent
になってます.
Posted by utatane at 2009年02月24日 09:14