はじめに
クライアントサイドMVVMフレームワークであるknockout.jsに関する話題。
数百〜数千件のデータを扱うときの注意点・ハマりどころについて。
ObservableArray
配列の監視を行うObservableArray
を用いる場合に、こんな処理を書くとパフォーマンスは著しく低下する。数百件のデータを扱うのに10秒近くかかる。
var ViewModel = function() {
this dataset = ko.observableArray([]);
this addDataset = function(items) {
var i;
var length = items.length;
for (i = 0; i < length; i++) {
dataset.push(new Result(items[i])); // ここがボトルネック
}
};
};
ここでのdataset.push()
はknockoutが提供するメソッドで、jsの標準関数と同等の働きをしつつ、リスナに対して変更を通知する(ちなみにdatasetはあくまで関数である)。この書き方では1件1件pushされる度に通知が飛ぶために、パフォーマンスは著しく下がる。オブジェクトを生成して1件1件配列に突っ込んでいるのだからもちろん遅いのだが、ドキュメントをさらっと読んだだけだとうっかりハマってしまうので注意。
knockoutの挙動で「?」となったら、ソースコードを見てみると案外すぐ分かったりする。
(今回はこの辺)
これを改善するためには、datasetの変更を1度で済ませる必要がある。通知自体は、observable.valueHasMutated()
関数によって行われるので、このような書き方が考えられる。参照を持つ配列に一度渡してから最後に通知する方式だ。
var ViewModel = function() {
var self = this;
this dataset = ko.observableArray([]);
this addDataset = function(items) {
var underlyingArray = self.dataset();
var i;
var length = items.length;
for (i = 0; i < length; i++) {
underlyingArray.push(new Result(items[i]));
}
self.dataset.valueHasMutated();
};
};
kncokout.utilsとapply()を用いる
もっとシンプルな方法は、knockout.utils内にある便利な関数を使うことだ。
var ViewModel = function() {
var self = this;
this dataset = ko.observableArray([]);
this addDataset = function(items) {
var newItems = ko.utils.arrayMap(items, function(item) {
return new Result(item);
};
self.dataset.push.apply(self.dataset, newItems);
};
};
大量のデータを配列に追加してViewに通知するときには、通知回数について意識すべきである。このような場面にはよく遭遇するので、覚えておきたい。