アップデート 2016/05/01:AngularJS 1.0 に比べて、React.js のほうが良いです。AngularJS の bi-direction binding、ng-repeat などは使い易いですが、DOM 操作が多くて、重いです。React.js も同じ機能が簡単に作れます。さらに、AngularJS 1.0 の Learning Curve が高いから、習うにはなかなか時間がかかります。React.js を使うと、どんどん Component が作れて、ますます開発効率が上がります。
追加 2016/03/07:最近、React.js を使うたびに、AngularJS は難しいなと感じ始めています。1.0 では概念が多くてなかなか使いにくいですね。React.js の場合、概念も簡単だし、Flux を使うと、アプリの構造はかなり簡単になります。また速いです。まぁ、速くするためには、shouldComponentUpdate 関数を細かく実装すればいいので、はっきりしています。今後続けて React.js を使おうと思います。新規プロジェクトの場合、AngularJS 2.0 を待ったほうがいいです。でも、古いバージョンのブラウザをサポートするためには、React.js のうほうがよさそうです。時間があれば、また http://aurelia.io/ を見てみようかと。Polymer はどうやら、遅いようなきがしています。。。後日、ちゃんと勉強してみよう。
最近、隣のグループは react.js に転向したので、再び AngularJS のパフォーマンス問題が提起されました。実際はどうですかね。。。
まぁ、個人的な感じでは、JavaScript 根本的にパフォーマンスが良くないです。AngularJS は良く重いと言われてます。特に Dirty Check の実行サイクルが考えば考えるほど重いと思われています。でも、どんなフレームワークでも、react.js、ember.js でも、そういう長いループが存在しています。だから、Dirty Check はそんなに大きいな問題でもないと思います。
問題は利用者側にあると思います。なぜなら、たぶん結構な数の開発者は $timeout や, $http の中に $rootscope.$apply 関数が呼ばれてると知らないです。それで、無意味に $timeout を呼んだり、自分でその中に $apply を使ったりしています。そうすると、AngularJS は2回 Dirty Check をやるので、重いですね。
つまり、フレームワークとして、AngularJS はあまり複雑で、なかなか全部理解するには時間がかかります。そのあと、チューニングとかもしないから、問題を発見して、解決するプロセスが欠けてます。
じゃ、どうしたら AngularJS のパフォーマンスが向上できますかね?
まず、$apply 関数を理解しましょう。$apply 関数は $digest 関数を読んで、もしエラーとか、Exception が発生したら、ちゃんとエラースタックをリストできます。(中に try-catch があるので)。$digest は登録した expression を実行して、過去の値と現在の値を比較して、変更があったら、変更の Handler 関数を呼びます。Handler 関数の中にもし何か値変更があったら、また $digest が実行されます、変更がないまで、実行し続けます。それで、$digest が10回まで繰り返し呼ばれます。10回以上の場合、Exception が Throw されます。
それで、もし登録した expression の数が最小限に抑えるなら、そのサイクルが短くなります。AngularJS 1.3 から {{::express}} が導入されました。もし expression が最初の一回のみ値を設定する必要があるなら、:: で一回のみ binding されます。これも bind-once という関数があります。また bindonce という別のライブラリもあります。
あと、すべての HTML 要素を ng-app 範囲にする必要がないので、例えば ng-non-bindable を使ったりして、directive の parse 範囲を小さくすることができます。以前も書いたように、jQuery UI を使うと、かなりの量の HTML 要素が作られます。それを parse するあまり必要ないし、"IE FIX" コメントも追加されますので、たまに Widget を壊します。だから、必要のない部分はもう non-bind して、apply directive 過程を止めます。
次、1ページはすべて AngularJS の ng-app 範囲にすると、かなりの expression 量が増えますので、controller を親子にしないで、独立のスコープなら、もうそのスコープの $digest 関数を呼ぶとか、Dirty Check の量を減らせます。
まぁ、よほど大きい web app じゃない限り、AngularJS のパフォーマンスは悪いと言えないと思います。すべて使い方次第ですね。
どんなフレームワークを使うと、必ず overhead が発生しますので、良くデザインして、必要な部分と必要ない部分をちゃんと分けて、パフォーマンスは良くなります。
#余談ですが、jQuery のパフォーマンスはかなり悪いです。通常の Vanilla JS を使うと、同じ関数が jsperf.com でテストすると、なん百倍の差が出ます。なぜなら、ライブラリだから、エラーチェックや、browser の違いを綺麗にカバしたいからです。以前 iOS の WebView も jQuery を使うと重く感じましたので、jQuery Lite を作って、パフォーマンス向上できました。使い方次第ですね。
Code
2015年2月10日火曜日
2014年8月27日水曜日
Angular Scope 外の HTML を Angular Scope に入れる
最近、ある旧 Page を Angular を使っている新しい Page に入れたかったのですが、Angular の Bootstrap は HTML を入れる前に行われていて、Scope が使われてないことがありました。
まぁ、解決にはそんなに難しくないですが、解決のプロセスが面白いから、メモしておこう。
まず、angular.element を取得します。その後、injector を取得して、$compile を変数に保存します。
var compile = angular.element(ele).injector().get("$compile")
これで、$compileProvider で作成した $compile 関数が取得できました。
その後、HTML String を $compile に当てて、Scope を利用して、リンクした HTML を該当場所に入れます。
compile(angular.element(ele).scope())
最後に、Scope の $digest を呼び出すために、$apply() 関数を呼び出す。終わり。
Angular は自分の実行サイクルがあるので、その外で生成した HTML はそのサイクルに入らないので、これを入れるために $apply を利用します。ーーまたは $timeout を使います。(実際、$timeout の中に $rootScope.$apply() が呼び出されています。)
短くいうと、HTML String を Angular の世界に入れるため、$compile を使います。$compile 関数は Link 関数を返しますので、link はある Scope をベースに実際の Angular の世界に入る HTML String を返されます。その後、$apply で $digest を呼び出して、一回 Angular の世界をリフレッシュします。
愚痴:1.0.8 に ng-if がないので、作ろうかなと思ってますけど、、、否決されました。しばらく ng-switch を使う予定です。1.2 を使いたいな〜。
まぁ、解決にはそんなに難しくないですが、解決のプロセスが面白いから、メモしておこう。
まず、angular.element を取得します。その後、injector を取得して、$compile を変数に保存します。
var compile = angular.element(ele).injector().get("$compile")
これで、$compileProvider で作成した $compile 関数が取得できました。
その後、HTML String を $compile に当てて、Scope を利用して、リンクした HTML を該当場所に入れます。
compile(angular.element(ele).scope())
最後に、Scope の $digest を呼び出すために、$apply() 関数を呼び出す。終わり。
Angular は自分の実行サイクルがあるので、その外で生成した HTML はそのサイクルに入らないので、これを入れるために $apply を利用します。ーーまたは $timeout を使います。(実際、$timeout の中に $rootScope.$apply() が呼び出されています。)
短くいうと、HTML String を Angular の世界に入れるため、$compile を使います。$compile 関数は Link 関数を返しますので、link はある Scope をベースに実際の Angular の世界に入る HTML String を返されます。その後、$apply で $digest を呼び出して、一回 Angular の世界をリフレッシュします。
愚痴:1.0.8 に ng-if がないので、作ろうかなと思ってますけど、、、否決されました。しばらく ng-switch を使う予定です。1.2 を使いたいな〜。
2014年7月15日火曜日
AngularJS Scope と $apply
今日時間ができたので、Angular の動きをちょっと追ってみました。module の登録は一つの難しいところですし、injector という概念も難しいと思います。ただ、実際使うとき、Scope というのはかなり重要です。
基本的にAngularJS は Compile 段階で、必要に応じて、Scope を作ります。さらに ng-scope というクラスを該当要素に追加されます。Scope を取得するには、angular.element(el).scope() で scope object が取得できます。ng-app の要素に対して、rootscope が作られます。
Scope は一つの object ですので、その prototype 中に、$apply という関数はあります。これはどういうとき使うかというと、Angular の世界に何かイベントを引き起こす時使います。イベントを引き起こすというのは model を変更したり、メッセージを $emit, $broadcast したりする場合です。
さらに遡ると、JavaScript の中に、イベントハンドラーは、jQuery の proxy などの bind 関数を使わない限り、基本的に window Scope で実行されるので、window Scope は Angular の Scope と違うため、単純にその中のでデータを修正したりすると、Angular の $digest 処理が実行されません。そうすると、$watch のデータのリフレッシュなどは Angular が検知できなくて、画面のリフレッシュもできません。
一つの例として、もし directive の link 関数で、要素に何かイベントハンドラーを bind したら、さらにこのイベントが発生したことを、 Angular の世界にそれを通知する場合、該当要素の Scope を取得して、$apply 関数を呼び出す必要があります。
if (ctrl.$viewValue !== value) {
scope.$apply(function() {
ctrl.$setViewValue(value);
});
実際のソースコードを見てみると、Angular は compile 段階で、HTML のテンプレートを保存したり、$$watcher に expression を追加したりしています。その後、 link 関数を返して、呼び出されます。その link 関数の中では、イベントハンドラーの追加や、jQuery UI の呼び出すことなどができます。さらに、何か変化がありましたら、Scope の $apply が呼び出されますので、 Angular の loop に入って、HTML のリフレッシュが実行されます。
例えば、ng-model directive の実行順番は:
Angular が bootstrap するとき、すべての directive を compile して、link 関数を呼び出します。このとき、ng-model の入っている要素の change イベントにイベントハンドラーをバンドルされます。キーボードのキーを押したとき、この change イベントが発生しますので、ハンドラー関数が呼び出されます。
その後、 interpolation がこの directive を $watch リストに追加します。-> すべての directive とモジュールを初期化完了したら、Angular の loop から抜き出します。
もしユーザーが何かのキーを input に押したら、ng-model のイベントハンドラーが呼ばれて、$apply 関数で model の value を変更します。
$apply 関数の中に $digest 関数が呼ばれて、すべての登録した $watch 関数を一通り実行されます。
これが終わったら、interpolation が実行されますので、HTML の変更などが実施されます。
次、キーを押したイベントハンドラーの実行が終わりますので、Browser がリフレッシュされます。
ちょっと複雑のように見えますが、実際どの directive もこのようなループが実行されます。一つ理解すれば、Angular はどのように動くかはたいてい明確になります。
それでは。
基本的にAngularJS は Compile 段階で、必要に応じて、Scope を作ります。さらに ng-scope というクラスを該当要素に追加されます。Scope を取得するには、angular.element(el).scope() で scope object が取得できます。ng-app の要素に対して、rootscope が作られます。
Scope は一つの object ですので、その prototype 中に、$apply という関数はあります。これはどういうとき使うかというと、Angular の世界に何かイベントを引き起こす時使います。イベントを引き起こすというのは model を変更したり、メッセージを $emit, $broadcast したりする場合です。
さらに遡ると、JavaScript の中に、イベントハンドラーは、jQuery の proxy などの bind 関数を使わない限り、基本的に window Scope で実行されるので、window Scope は Angular の Scope と違うため、単純にその中のでデータを修正したりすると、Angular の $digest 処理が実行されません。そうすると、$watch のデータのリフレッシュなどは Angular が検知できなくて、画面のリフレッシュもできません。
一つの例として、もし directive の link 関数で、要素に何かイベントハンドラーを bind したら、さらにこのイベントが発生したことを、 Angular の世界にそれを通知する場合、該当要素の Scope を取得して、$apply 関数を呼び出す必要があります。
if (ctrl.$viewValue !== value) {
scope.$apply(function() {
ctrl.$setViewValue(value);
});
実際のソースコードを見てみると、Angular は compile 段階で、HTML のテンプレートを保存したり、$$watcher に expression を追加したりしています。その後、 link 関数を返して、呼び出されます。その link 関数の中では、イベントハンドラーの追加や、jQuery UI の呼び出すことなどができます。さらに、何か変化がありましたら、Scope の $apply が呼び出されますので、 Angular の loop に入って、HTML のリフレッシュが実行されます。
例えば、ng-model directive の実行順番は:
Angular が bootstrap するとき、すべての directive を compile して、link 関数を呼び出します。このとき、ng-model の入っている要素の change イベントにイベントハンドラーをバンドルされます。キーボードのキーを押したとき、この change イベントが発生しますので、ハンドラー関数が呼び出されます。
その後、 interpolation がこの directive を $watch リストに追加します。-> すべての directive とモジュールを初期化完了したら、Angular の loop から抜き出します。
もしユーザーが何かのキーを input に押したら、ng-model のイベントハンドラーが呼ばれて、$apply 関数で model の value を変更します。
$apply 関数の中に $digest 関数が呼ばれて、すべての登録した $watch 関数を一通り実行されます。
これが終わったら、interpolation が実行されますので、HTML の変更などが実施されます。
次、キーを押したイベントハンドラーの実行が終わりますので、Browser がリフレッシュされます。
ちょっと複雑のように見えますが、実際どの directive もこのようなループが実行されます。一つ理解すれば、Angular はどのように動くかはたいてい明確になります。
それでは。
登録:
投稿 (Atom)