2011年12月17日

JavaScriptの無名関数の実行 (function(){})() と (function(){}()) の違い


JavaScript Advent Calendar 2011 (オレ標準コース) 17 日目、polygon_planet です。

ずっと Advent Calendar 参加してみたいなぁと思ってたんですが
ネタが思いつかない日々で半分諦めてたんですが、考え過ぎな気がしてきたので
別におもしろい記事でもないし、技術的にも参考になるのか不明ですがとりあえず書きます。

もしかしたら同じようなこと解説してる記事がすでにあるかも…(うまく検索できてない)


JavaScript で無名関数をその場で実行するとき、
(function() {
// 処理
})();
という書き方が主流っぽいですが、
(function() {
// 処理
}());
こう書いたらどう違うのか。

ちなみに、
function() {

}(); // エラー
これは式ではなく文として扱われるためエラーです。
ただし、以下のように実行する場合はエラーではありません。
var arr = [function(){}()];        // [undefined]
var obj = {foo: function(){}()}; // {foo: undefined}

(function(){})() vs (function(){}())

どちらも同じように実行され、結果的には同じなので
とりあえず、無名関数が生成され実行するまでの JavaScript コードの過程を追ってみます。

今回は手元に SpiderMonkey に付いてる JavaScript shell しかなかったので、それを使います。
わりと前にコンパイルしたやつですが JavaScript バージョンは 1.8 なので現行と大差ないと思います。
中間コードは js shell の dis で逆アセンブルして確認します。

まず (function(){})() から。
$ js
js> dis(function() { (function(){})() });
flags: LAMBDA INTERPRETED
main:
00000: anonfunobj (function () {})
00003: group
00004: pushobj
00005: call 0
00008: pop
00009: stop

Source notes:
0: 5 [ 5] pcbase offset 5
anonfunobj というは、無名関数を定義していると思われますが、
確認のため Mozilla のソースコードを見てます。

今回は js shell のバージョンに合わせるため
/mozilla-central/ ではなく /mozilla/ を参照します。

jsopcode.tbl
305 /* Auto-clone (if needed due to re-parenting) and push an anonymous function. */
306 OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 19, JOF_OBJECT)
このあたりの JSOP_* から参照できます。
JSOP は jsopcode.c あたりで確認できます。
3777               case JSOP_ANONFUNOBJ:
3778 #if JS_HAS_GENERATOR_EXPRS
3779 sn = js_GetSrcNote(jp->script, pc);
3780 if (sn && SN_TYPE(sn) == SRC_GENEXP) {
3781 JSScript *inner, *outer;
3782 void *mark;
3783 SprintStack ss2;
3784
3785 LOAD_FUNCTION(0);
3786 inner = fun->u.i.script;
// ... (長いので省略) ...
ここでは LOAD_FUNCTION が使われています。

JavaScript バイトコードインタプリタの jsinterp.c を参照して無名関数の生成から追ってみます。
5744           BEGIN_CASE(JSOP_ANONFUNOBJ)
5745 /* Load the specified function object literal. */
5746 LOAD_FUNCTION(0);
5747
5748 /* If re-parenting, push a clone of the function object. */
5749 parent = js_GetScopeChain(cx, fp);
5750 if (!parent)
5751 goto error;
5752 obj = FUN_OBJECT(fun);
5753 if (OBJ_GET_PARENT(cx, obj) != parent) {
5754 obj = js_CloneFunctionObject(cx, fun, parent);
5755 if (!obj)
5756 goto error;
5757 }
5758 PUSH_OPND(OBJECT_TO_JSVAL(obj));
5759 END_CASE(JSOP_ANONFUNOBJ)
ここでも LOAD_FUNCTION が使われているので定義を追ってみます。
jsinterp.c
2604 #define LOAD_FUNCTION(PCOFF)                                                  \
2605 JS_GET_SCRIPT_FUNCTION(script, GET_FULL_INDEX(PCOFF), fun)
JS_GET_SCRIPT_FUNCTION を実行するマクロになっています。

じゃあ JS_GET_SCRIPT_FUNCTION はどうなってるの
jsscript.h
143 #define JS_GET_SCRIPT_FUNCTION(script, index, fun)                            \
144 JS_BEGIN_MACRO \
145 JSObject *funobj_; \
146 \
147 JS_GET_SCRIPT_OBJECT(script, index, funobj_); \
148 JS_ASSERT(HAS_FUNCTION_CLASS(funobj_)); \
149 JS_ASSERT(funobj_ == (JSObject *) STOBJ_GET_PRIVATE(funobj_)); \
150 (fun) = (JSFunction *) funobj_; \
151 JS_ASSERT(FUN_INTERPRETED(fun)); \
152 JS_END_MACRO
JSFunction にキャストして JavaScript 関数オブジェクトになることが確認できました。
(これは現行の JavaScript だともっとてっとり早くなってる)

こんな感じで追ってみると、anonfunobj は無名関数の生成ということがわかります。

次の group は、
jsopcode.tbl で同じく定義されています。
317 /* Parenthesization opcode to help the decompiler. */
318 OPDEF(JSOP_GROUP, 131, "group", NULL, 1, 0, 0, 19, JOF_BYTE)
jsopcode.c では以下のあたり。
2066               case JSOP_GROUP:
2067 cs = &js_CodeSpec[lastop];
2068 if ((cs->prec != 0 &&
2069 cs->prec <= js_CodeSpec[NEXT_OP(pc)].prec) ||
2070 pc[JSOP_GROUP_LENGTH] == JSOP_NULL ||
2071 pc[JSOP_GROUP_LENGTH] == JSOP_DUP ||
2072 pc[JSOP_GROUP_LENGTH] == JSOP_IFEQ ||
2073 pc[JSOP_GROUP_LENGTH] == JSOP_IFNE) {
// ...(中略)...
2100 }

無名関数オブジェクトを生成し、それを括弧 () によって式とし、
そしてそれを call というような流れになっています。


続いて (function(){}()) のほうも同じように。
$ js
js> dis(function() { (function(){}()) });
flags: LAMBDA INTERPRETED
main:
00000: anonfunobj (function () {})
00003: pushobj
00004: call 0
00007: pop
00008: stop

Source notes:
0: 4 [ 4] pcbase offset 4

なんと、(function(){})() と比べると
group がないだけで、ほかは同じになってしまいました。

無名関数に対する Parenthesization が発生しないので、
ちょうど、上で書いた [function(){}()] のように実行されています。

この結果から、

(function(){})() よりも、 (function(){}()) のほうが
コストが減るとわかりました。


とは言っても、どちらを使用しても実行時間が変化するとしてもマイクロ秒単位などで
JavaScript でそこまでクリティカルな処理が存在するのかも微妙なところで
あまり気にしないでいい気がします。


でも一応ベンチマークをとってみます。
js> for (var t = +new Date, i = 0, f; i < 10000000; i++) f = (function(){}());
print(+new Date - t);
10437
js> for (var t = +new Date, i = 0, f; i < 10000000; i++) f = (function(){})();
print(+new Date - t);
10485

結果:
  • (function(){}()) : 10437 ms.
  • (function(){})() : 10485 ms.

たしかに (function(){}()) のほうが速かったんですが、
10,000,000 回ループして わずか 48 ミリ秒の違い。
これを懸念してる人って…(いるのでしょうか)


そういえばこの違いについてはちょっと前に Twitter で話題(?) になって気がします。
もしかしてもうまとめられてたのかな…。だとしたら……考えたくない。


とりあえずモヤモヤした感じがなくなったのでよかった。
たまにソースコード確認してみると新鮮デスネ!


2011年12月6日

JavaScript非同期ライブラリ PotLite.js 1.23 リリース

JavaScript 非同期処理ライブラリ PotLite.js 1.23 リリースしました。

Version 1.23 は、ほとんどバグフィックスです。

AOP ぽいことができる Pot.Signal がひどくて、
シグナル解除して再度登録すると重複してコールバック関数が呼ばれちゃったりしてたので
がんばって可能な限り実行テスト増やしてフィックスして
やっと想定の動きになりました。

前バージョン (1.22) を使ってる方いたら申し訳ないです。。

PotLite.js

PotLite.js は、めんどうになりがちな非同期処理をとにかく楽にコーディングできるよう
直感的に記述できる Deferred オブジェクトを中核として実装しています。
そして、ユーザー (UI) への配慮を目的として
CPU など負荷のかからないループ処理やイテレータが利用できる JavaScript ライブラリです。


経緯などについては、以前の記事 や、 CPU 使用率のベンチマーク結果の記事 など
シグナル (イベント) については PotLite.js 1.22 リリース - アスペクト指向っぽく書けるSignal実装
などの記事を参照ください。

ダウンロード

PotLite.js 1.23

HEAD (常に最新)

レポジトリ



以下は PotLite.js について情報です。
レポジトリにも同じようなこと書いてあるので
インストールや概要など、不要な場合は読み飛ばしてください。
マニュアルのリンクは下にあります。

動作環境

以下の Web ブラウザで動作確認済みです。

  • Mozilla Firefox *
  • Internet Explorer 6+
  • Safari *
  • Opera *
  • Google Chrome *

また、以下の環境でも動作するよう設計されています。

  • Greasemonkey (UserScript)
  • Mozilla Firefox Add-On (on XUL)
  • Node.js
  • Other non-browser environment

インストール

一般的な方法で動作します。

例:

<script type="text/javascript" src="potlite.min.js"></script>
<!--または-->
<script type="text/javascript" src="http://api.polygonpla.net/js/pot/potlite/1.23/potlite.min.js"></script>

Node.js の場合。

// Example to define Pot object on Node.js.
var Pot = require('./potlite.min.js');
Pot.debug(Pot.VERSION);

Pot.Deferred.begin(function() {
    Pot.debug('Hello Deferred!');
}).then(function() {
    // ...
});
// ...

Greasemonkey (userscript) の例。

// ==UserScript==
// ...
// @require  https://github.com/polygonplanet/Pot.js/raw/master/potlite.min.js
// ...
// ==/UserScript==
Pot.Deferred.begin(function() {
    return Pot.request('http://www.example.com/data.json').then(function(res) {
        return Pot.parseFromJSON(res.responseText);
    });
}).then(function(res) {
    Pot.debug(res);
    // do something...
});
//...
PotLite.js をバージョンを限定して Web から直接読み込みたい場合、
上の GitHub リンクでは常に最新になってしまうため
実装の差異による不具合が発生するかもしれません。
そのため、ホスティング用 API サーバを用意しています。
これは、1.23 の部分をリリース済みのバージョンに合わせて変更できます。
レポジトリに (例えば 1.xx と) バージョンをタグ付けした時に、
あわせて API サーバに置くようにしています。

例えば Greasemonkey で 1.23 を使いたい場合、
// ==UserScript==
// ...
// @require  http://api.polygonpla.net/js/pot/potlite/1.23/potlite.min.js
// ...
// ==/UserScript==
と記述できます。

Greasemonkey に限らず script タグからでもなんでも自由に使ってください。


jQuery プラグインとしての例:

// jQuery を読み込んだ後に実行。
Pot.deferrizejQueryAjax();

// Ajax 系の関数が Pot.Deferred を返すようになる
$.getJSON('/hoge.json').then(function(data) {
    alert(data.results[0].text);
}).rescue(function(err) {
    alert('Error! ' + err);
}).ensure(function() {
    return someNextProcess();
});

// エフェクトなどを Deferred 化する 'deferred' が追加される
$('div#hoge').deferred('hide', 'slow').then(function() {
    // .hide() が終了したあとの処理
});

Pot.deferrizejQueryAjax() は v1.23 現在、ライブラリ側からは自動で実行しません。
なので、プラグインとして利用する場合は コードの最初などで一度コールしてください。

リファレンス・マニュアル

より詳しい情報はすべてリファレンスに載っています。
基本的な導入や、各メソッド・関数についても扱っています。

動作テスト

以下のページで動作テストができます。
ページを開くと実装されている主な関数・メソッドを全てテストします。

自動生成されたドキュメント

Closure Compiler によりソースコードから自動生成されたドキュメントです。

殆どの関数ごとにサンプルコードを載せているので、ある程度は参考になると思います。
生成物をすべて確認しているわけではないので、誤認識してる箇所もあるかもしれません。
より詳細な実装などは直接ソースコードを参照ください。




不明な点、要望やバグや感想などありましたら
@polygon_planet や 下のレポジトリから、またはメールでなんでも伝えてください。

レポジトリ

2011年12月5日

マウス下の画像をGoogle画像検索するTomblooパッチ

Firefox アドオン Tombloo のパッチです。

機能概要

マウス下の画像を Google 画像検索 (Google Image Search) で検索できるようになります。
Taberareloo には既に付いていて、せっかくなので作りました。
あと、二次画像詳細検索 も選べます。

コンテキストメニューの 「Tombloo」 から操作できます。


今のところメニューは 2 つだけですが、
なにか思いついたら追加して随時アップデートする予定です。

パッチ (Download or Update):

インストール/アップデート:

上のパッチリンクを
右クリック→「Tombloo」→「Tomblooパッチのインストール」でインストール/アップデート。


※直接右クリックからインストールできない場合:

できない場合は

「Tomblooパッチのインストールに失敗しなくなるパッチ」 のパッチ/記事をインストールもしくは参照ください。

レポジトリ