@nqounetです。

JavaScript(というか、ほぼjQuery)にも大分慣れてきた気がしてきた今日このごろですが、皆さまいかがお過ごしでしょうか?

配列は展開しなくても大丈夫だった

やりたいのは、Perlでいう、デリファレンスのようなことなのです。

1
2
3
4
5
use List::Util qw(sum);
my $array = [1, 2, 3, 4, 5];
sub func { print sum(@_); }

func(@$array); # ここで$arrayを展開している

ありとあらゆる知識を総動員して検索したのですが、こんなことはする必要がなかったようです。

結論としては、applyを使えばいいです。

applyを使うと、配列のままでも展開して渡したようにできるという、まるで魔法ですね。

1
2
3
4
var array = [1, 2, 3, 4, 5];
var func = function(a, b, c, d, e){ alert(a + b + c + d + e); };

func.apply(this, array); // 配列をそのまま渡しても動くという謎仕様

引数のように見えるthisは特に意味は無いようで、ここはnullでも0でもなんでも良いです。

ここからは、何故こういうことをしようとしたのかをツラツラと書いていきます。

AJAX万歳

AJAXでAPIにアクセスしてデータを取得し、それに基づいてゴニョゴニョする、という処理は頻繁にあると思います。

こういった非同期な処理を書こうとすると、コールバックばかりになってしまいます。

コールバックというのは、読み込みが終わったあとの処理のことで、jQueryではAJAXのgetの処理にコールバックも一緒に渡してやると、getしてきたコンテンツを引数にして、コールバックを実行してくれます。

1
2
3
4
5
// 非同期通信
$.get(url, function(contents){
console.debug('contents:', contents);
/* ゴニョゴニョ */
});

キャッシュ万歳

それとは別の話ですが、サーバーとの通信は時間がかかるので、AJAXの結果はキャッシュしておいて、必要なときはキャッシュから取得するように作ると思います。

しかも、読む方からはキャッシュがあるかどうかを気にせずに。

キャッシュがあった場合は、非同期ではないので、値を直接受け取ることができます。

なので、できればこういう感じで書きたいなと思うのです。

1
var contents = getContentsByAjax(url);

しかし、残念ながら、キャッシュがなかった場合にもこのような書き方で動作させる方法が全くわかりません。

そして、間に一つでも非同期の処理が入ると、続けての処理も普通に書くことができなくなります。

どこかでリセットしたいと思うわけです。

そこで、イベントオブジェクトを経由して、同期的な処理として書いてみることにしました。

イベントオブジェクト経由でも同じように実行する

先に書いたとおり、applyを使えば配列を展開する必要がありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict';

jQuery(function($, undefined){
var eventHoge = function(e){
var args = Array.prototype.slice.apply(e.data);
hoge.apply(this, args); // hogeを呼び出し
},
hoge = function(foo, bar){
console.debug('hoge arguments:', arguments);
},
init = function(foo, bar){
console.debug('init arguments:', arguments);
hoge(foo, bar); // hogeを呼び出し
$(window).one('hoge', arguments, eventHoge);
$(window).triggerHandler('hoge');
};
init('foo', 'bar');
});

こうするとhoge arguments:で出力される値は、initの中から呼び出したものと、eventHogeの中から呼び出したものとがおなじになります。

argumentsを使いたいからこそ、こんなにややこしいことになっているのですが、それをするだけの価値はあると思っています。

コールバック方式で書いた一つの例

イベントオブジェクト方式で書いたのが手元にないので、とりあえずコールバック方式のほうを晒しておきます。

AJAXでテンプレートファイルを取得して、それを使ってゴニョゴニョするスクリプトです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
'use strict';

jQuery(function($, undefined){
var DEBUG = 1,
lscache,
Mustache,
$runButtons,
generateKeyFromPath = function(path){
return 'fetched:' + path;
},
fetchTemplate = function(path, $cb){
var key = generateKeyFromPath(path);
$.get(path, function(template){
console.debug('run $.get');
lscache.set(key, template, 1);
return $cb.fire(template);
});
},
getTemplate = function(path, $cb){
var key = generateKeyFromPath(path);
var template = lscache.get(key);
if (template) {
return $cb.fire(template);
}
else {
fetchTemplate(path, $cb);
}
},
render = function(targetId, targetName){
var obj = {
id: targetId,
name: targetName
};
var $cb = $.Callbacks();
$cb.add(function(template){
var rendered = Mustache.render(template, obj);
$('#' + targetId).html(rendered);
});
getTemplate('templates/init.mst', $cb);
},
changeName = function(e){
var $this = $(e.currentTarget);
render($this.data('id'), $this.html());
},
setVars = function(){
lscache = window.lscache;
Mustache = window.Mustache;
$runButtons = $('button[data-run=changeName]');
},
initHandlers = function(){
$runButtons.on('click', changeName);
},
init = function(){
setVars();
initHandlers();
if (DEBUG) {
lscache.flush();
}
};
init();
});

renderの中がスッキリしない感じです。

jQuery以外に以下のライブラリを使っています。


  • pamelafox/lscache


    • ローカルストレージにキャッシュを作成してくれる便利なライブラリ


  • janl/mustache.js


    • オブジェクトを渡すだけで使えるテンプレートエンジン


動かしてみたい方はgithubにあげてありますのでお試しください。