飴屋

Vue.js/日記004

仮想DOM

今回Vue.jsのことを調べるきっかけになった案件で、実際にVue.jsが導入されました。その結果、1分ぐらいかかっていたDOMの操作が半分くらいに縮まったという成果が得られました。そもそも、そんな大量なDOM操作を一度に行っていることがおかしかったので、Vue.jsが最終的なプロダクトに採用されるのかはまだわかりませんが、一つの選択肢にはなっているようです。

なんでVue.jsの方がDOM操作から描画までが速いのかについては、正確に把握しているわけではないのですが、仮想DOMの利用が主たる要因なのだそうです。仮想DOMってなんだよって思って検索してみても、あんまり明確な回答に巡り合えなかったのですが、DocumentFragmentがその正体なんじゃないかと勝手に思っています。実DOMがDocumentにぶらさがっているノードであるとすると、仮想DOMはDocumentにぶら下がっていないノード、つまりDocumentFragmentなのでしょう。実DOMは内容が更新されると描画のための計算があれこれ必要になりますが、仮想DOMにはそれがありません。この仮想DOMの中の必要な部分をDocumentにぶら下げたタイミングで初めて、in-documentフラグというのが立って、描画に必要なあれこれの計算が始まります。描画のための計算というのは、表示要素のレイアウトを決定する作業ですね。img要素であれば、画像ファイルを読み込んで画像の縦横サイズを測ったり、table要素があれば、表の中身から適切な列幅を計算したり、CSSで規定されている幅や高さ、アニメーション・・・もろもろを計算したり、やることは盛りだくさんです。これを実DOM更新の度に実行するとなると、複数回の実DOMの更新が重なった際に、重複する作業が増えます。この重複分がオーバーヘッドとなっているわけですね。

仮想DOMは何度更新してもそういった描画のためのコストはかかりません。(描画されないから)それを一度に実DOMに反映させれば、オーバーヘッドになっていた分の重複作業が省けて、更新にかかる時間が少なくて済むのですね。Vue.jsにはその実DOMへの反映の際にも必要最低限の更新で済むような工夫がなされているのだそうですが、具体的なことはまだ知りません。

実DOMへの反映

さて、先にMVVM構造について触れてあったと思います。データとしてのModelを更新すると見た目としてのViewが連動して更新される、逆もまた然り。これをViewModelとしてのVue.jsが担ってくれます。Viewの方にはv-modelディレクティブをつけておけば、いい感じにその内容をModelに反映してくれるらしいです。これは例えば入力用のUI(テキストボックス、リストボックス、各種ボタン・・・)の更新イベント(change click keydown・・・)を検知して実現してくれているのだと思います。では、Modelへの変更がViewに反映されるはどうなっているのでしょうか?Modelの中身が変更されたら通知してくれるようなイベントは聞いたことがありません。

var vm = new Vue({
el:'#app',
data:{
str: '',
list: []
}
});

vm.str = 'hello';
vm.list.push('world');

このコードではstr(文字列)、list(配列)のデータを更新しています。配列についてはpushのようなメソッドをオーバーライトして、メソッドが呼び出されたら更新フラグを立てる、みたいなことをしているのかなと予想しています。しかし、文字列の更新は代入演算子(=)で行っていて、JavaScriptは演算子のオーバーロードができなかった気がするので、どうやって更新を検知するのかなと疑問に思っています。また、配列の方も操作するために適切なメソッドを使わないと更新が検知されず、実DOMへの操作が反映されないなんてことがチョコチョコ起きています。その辺は慣れが必要なのかもしれません。(今まさにそこで悩んでいます。)

vm.$forceUpdate();

↑こんなのもあるようなので、試してみようかな。


(追記)

Objectへの変更はsetterを用意して検知するのかもなぁ。とか考えながら作業をしていたら、実際にObjectへの変更がVue.jsに検知されない事案が発生しました。具体的にはObjectに新たに要素を追加したり、要素を削除したときに発生しました。

var vm = new Vue({
el:'#app',
data:{
obj: {a:1,b:2}
}
});

//要素の削除 -> Vue.jsは変更を検知しない
delete(vm.obj.a);

//要素の追加 -> Vue.jsは変更を検知しない
vm.obj['c'] = 3;

これはもう現在のJavaScriptの仕様上の限界らしいです。なので、検知を促すようなメソッドを経由して追加や削除を行えばいいそうです。

//要素の削除
vm.$delete(vm.obj,'a');
Vue.delete(vm.obj,'a');

//要素の追加
vm.$set(vm.obj,'c',3);
Vue.set(vm.obj,'c',3);

書き方は二通りあるようですが、どちらも内容は同じようです。これでthis.$forceUpdate()とオサラバできました。

日記一覧

Last-Modified