2009年02月24日 04:30 [Edit]
javascript - ソースを見せてかつ動かすための3つのtips
livedoor Blogを私が愛用しつづけている理由のひとつが、JavaScriptを受け付けること。
おかげでかなりのentriesが溜まりましたが、それだけにで実行用のソースと表示用のソースを用意するのが人一倍おっくうに感じられます。そんなわけで、どうやれば怠慢をもっと発揮できるかをまとめてみました。
0. DOMにIDをふらずにデモる
こんな感じのデモがあったとしましょう。「404 Blog Not Found:javascript - Array#sortがオレquicksortより遅い!?」にあるものを書き直したものです。
よくあるのは、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
e.textContent || e.textContent
になってます.
ですね。:-)
// oops. fixed.
try{ eval(my.entry) }catch(e){ your.comment }
呼び出しが無名なのにqっていう名前をつけているのにはどういう意味があるのだろう…?