Sapporo.js - 2013.07.27に参加しました / Angular.js/Knockout.js についての感想など
Sapporo.js - 2013.07.27 に参加してきました。
今回は、 @tmaeda さんが Angular.js、@iakio さんが Knockout.js について発表をしてくださいました。
簡単にですが、お二方の発表についてのぼくの感想をまとめておきます。(主に自分のメモ用)
AngularJSのご紹介
"Angular.js はフレームワークではない! HTML の再実装だッ!(バーン"
- なるほど!という印象。今までは、HTML に独自拡張の属性をつけるのがあまり好きになれなかったんですが、こういう方針なら頷けます。
JSON の serialize/deserialize やコミットのタイミングなど、外部 API とのやり取りは特にサポートしていないのでプログラマが自分でなんとかする必要があるとのこと。
- (Ember Data は自前でなんとかしようとしているんですが、今一歩力及ばずな印象。そもそもこのあたりはフレームワークで解決できない、ドメインに特化した領域ななんですねぇ...)
変数名で DI を実現しているとことで、minify とは相性が悪そう -> 書き方で回避できるよ!
- ちょっとしんどいですよねぇ... minifier のオプション(あるのかな..?)とかで設定できるとよいんでしょうか?
Getting start with knockout.js
生の JavaScript の延長線上に存在している気がして、メソッド名を見るだけでもなんとかくどうなるかわかりやすい
"Knockout.jsはフレームワークではなくライブラ リだ(ズギャーン"
- なるほど、こういう Knockout.js はこういう位置づけなんですね
ko.computed
は依存性をプログラマが明示する必要がない、っていうのはかなりすばらしいと思いました。 -> "定義時に一回実行してみる"- こういう割り切りは大好きです。(Ember.js だと、自分で明示する必要があるので若干面倒です。そこまで人間が管理するの?っていうのはちょっとストレスフル。)
あまり大それたことをやっているライブラリではないので、Backbone.js を触ったことがあるひとならすぐに馴染めるのかな、という感じでした。ぼくは Knockout.js は触ったことがないですが。
その他
ちなみに、この MV* の空気を読まずぼくは Ruby の Middleman について発表してきました。
発表資料はこちら:
当日いただいた感想:
.@tricknotes 先生が #sapporojs で Ruby の話しかしていない。JS嫌いの私としては大変癒されます。
— Tomoki MAEDA (@tmaeda) 2013, 7月 27
今回、ぼくからの発表お願いを快く引き受けてくださったお二人には感謝の気持ちでいっぱいです。
今後共よろしくお願いいたします。
クライアントサイド JavaScript のパフォーマンス改善には backburner.js が便利!
DOM 処理や Ajax など、JavaScript が外の世界とやり取りする部分というのは、一般的に待ち時間を多く必要とします。
パフォーマンスを改善しようと思った時に、ロジック部分でコツコツと節約するより、まずコストが高い処理を行わないようにするということで、驚くほどの効果を経験をされたことはありませんか?
今までパフォーマンス測定をされた方であればピンとくる部分があることと思います。
そんな時に役に立つのが、今回ご紹介する backburner.js です。
backburner.js って?
backburner.js とは Ember.js の run loop モジュールから切りだされたとても小さなライブラリで、短時間に集中的に発生するメソッド呼び出しの回数を制限したい場合などに利用することができます。
backburner.js ってどう使うの?
例えば、JavaScript で特定のイベントハンドラ内で Ajax 通信を行う場合を考えてみましょう。
$('.action-area').on('mousemove', function(event) { // マウス位置を Ajax で送信 });
この例だと、.action-area
要素の上でマウスが動く度にマウス位置を Ajax で送信することになります。WebSocket でやり取りをしている場合でも無い限り、ここまで頻繁に通信を行うことはサーバにとって余分なコストとなる場合があります。
そこで backburner.js です。 上の例を以下のように書きなおしてみましょう。
var backburner = new Backburner(['send']); function sendMousePoint() { var event = this; // マウス位置を Ajax で送信... }; var wait = 100; $('.handle-area').on('mousemove', function(event) { backburner.debounce(event, sendMousePoint, wait); });
backburner.debounce
を使うことで、引数で指定されたメソッドは、同一の Function であれば一定の時間間隔(wait = 100 ms)の中で 1回しか実行されません。
とてもエコロジーですね。
また、いわゆる MVC フレームワークにおいて、モデルの変更をビューがバインドしていて、かつモデルに頻繁に更新が発生するといった場合についても応用出来ます。
これについては、 backburner.js の README に素晴らしくわかりやすいサンプルコードがあるので、こちらを見てみましょう。
モデルのプロパティをひとつひとつ変更していく、というのはよくありえるシチュエーションだと思います。
上述の例のように backburner.js を使うことで、チラツキやもっさり感の原因になりえる DOM の頻繁な更新を抑制することができます。
参考資料
もっと知りたくなった方は、作者の ebryn さんの発表動画とその資料をみてみると良いかもしれません。
- http://talks.erikbryn.com/backburner-and-the-run-loop/
- http://www.youtube.com/watch?v=VuIFdXmi080&feature=youtu.be&t=24m5s
それでは、みなさまも backburner.js のすばらしさをご体感くださいませ!
Ember.js をターミナルから触ってみる
最近 Ember.js にはまっています。 せっかくなので、ブラウザを使わずにターミナルからも ember を触れるようにしてみました。
使い方は簡単です。 まずはインストール。
$ npm install -g term-ember
そして実行します。
$ term-ember
あとは ember に触りたい放題です。
term ember> Ember.VERSION '1.0.0-rc.3' term ember> var App = Ember.Application.create(); ...
ちなみに、各種ライブラリのバージョンを指定して起動することもできます。
$ term-ember --ember 1.0.0-rc2 --jquery 2.0.0
ちょっとしたメソッドの動作確認などに使うと便利かもしれません。
JavaScript 道場を開催しつつ参加してきました
しっかりコードを書きながら他の人のやり方や考え方を共有することができるイベントに参加したいなぁという思いが高まってきていたので、JavaScript 道場というイベントを開催しつつ参加してきました。
JavaScript 道場って?
JavaScript 道場 - connpass の概要がとてもよくまとまっています。
JavaScript 道場は、JavaScript での実践的なコードについて議論をしながらグループでコードを書くイベントです。 普段 JavaScript を書いていて、なかなか上手にできない部分や、汚くなってしまいがちな部分について、みんなで解決方法を模索します。 JavaScript 道場では、JavaScript のプロフェッショナルを師範としてお呼びして、 彼らにアドバイスをもらいながらコードを書くことで、彼らの持っているノウハウを知ることができる機会をご用意いたします。 ぜひ、みなさまが普段から困っている部分を直接質問してみましょう。 Node.js 側の師範として @badatmath 氏を、ブラウザ側での師範として @hokaccha 氏をお迎えします。 JavaScript 道場は、セッションやハンズオンなどのイベントとは違って、考えながらコードを書くイベントです。 目の前のコードに挑むための工夫を、ぜひみなさんのプロジェクトにも持ち帰ってください。 「JavaScript をもっとキレイに書きたい」、「整理されたコードについてもっと考えたい」そんな悩みを抱えている方はぜひお越しください。
あえてひとことでまとめると、チームでコードを書いてお題に取り組むイベントです。(ざっくり)
開催の理由
「ありがたい話を聞く」系のイベントだと、自分で東京に行けばいろいろな種類のイベントに参加することができるし、自分にはあえて札幌で開催する理由はありません。
もちろん、そういうイベントに参加するのはそれで楽しいんですが、現場との距離感というか「明日から自分の仕事につながるもの」をちゃんと持ち帰れるようなイベントに参加したいなぁという気持ちがずっと自分にはありました。
ただ、今までチームでコードを書くイベントはあまり聞いたことがなかったので、せっかく開催するなら自分が参加したいイベントにしたい、という思いで開催を決意しました。 (ちなみに、今回の JavaScript 道場開催にあたって一番影響を受けたイベントは TDDBC in 札幌 です。)
また、このイベントを通じてぼくが実現したかったことは、「自分の身近で JavaScript についてもっと喋れる場ができること」です。 (こう思ったきっかけは、東京のカンファレンスに行くと、ほかの参加者の方々とわりとコアな話で盛り上がれるので、自分が住んでいる札幌でももっとカジュアルに話ができるといいなぁと思ったことでした。)
そのためには、実際にチームでコードに触れて一緒に考えるという経験が一番必要なのではないかと考えたのです。
内容について
午前の部では、 JavaScript を現場で使われている師範をお呼びして、JavaScript を使った開発の話を聞きました。
そのときの講演資料はこちらです:
- 「フロントエンドJavaScript における設計とテスト」( by @hokaccha )
- 「テストを作りながら学ぶ、サーバサイドJavaScript開発」 ( by @bad_at_math )
そして午後の部では、チームでお題に取り組みました。
お題って?
お題といっても、だいぶざっくりした「お客さんの要望」のような抽象的なものだけを用意して、それに対してどうやってアプローチしていくかは完全にチームに委ねるという形にしました。
そして今回のお題はこちらでした:
このうち、どちらのお題に挑戦するかをチームで決めて、好きな方のお題に取り組んでもらいました。
ここでの時間配分は以下の通りです:
時間 | 内容 |
---|---|
13:30 - 13:40 | お題発表 |
13:40 - 17:30 | お題 |
17:30 - 17:50 | チームでの感想戦 |
17:50 - 19:20 | チームごとの成果発表 |
ただ、決まったコードを書くだけではなく、仕様を決めて実装して、そして最後には自分たちの開発を振り返ることができるよう設計したつもりです。 (つまり、ふつうの開発の短縮版にできるよう意識しました。)
各チームでの成果物へのリンクをこちらにまとめています:
最後に
運営者としても参加者としてもとても楽しかったので、ちゃんと開催できてよかったと思っています。 (もちろん、上手くできなかったところやもっと上手くできそうなところはいっぱいあるので、ちゃんと振り返って次につなげます)
師範としてご協力いただいた @bad_at_math, @hokaccha, 一緒に運営してくれた @volpe_hd28v, @puprl, @hachiilcane, そして、参加してくれた方々に深く感謝します。
そして、「次回」というのはすぐには難しいですが、何らかの形でこれからにつながることを考えていきたいですなぁ。
参照リンク
- JavaScript 道場 #jsdojo - Togetter (@shuji_w6e がまとめてくださいました!)
- JavaScript 道場に参加した - ヽ(´・肉・`)ノログ
- sapporojs/jsdojo - GitHub (実行委員のコミュニケーションに使いました)
Node.js の EventEmitter のコードを読んで、速度最適化されたコードについて考える
大都会岡山 Advent Calendar 2012 20日目のエントリーです。
前回の記事は @DAI199 さんによる 中国で流行っていること、感じたこと(大都会岡山 AdventCalender2012 19日目) - tagamidaiki.com でした。
20 日目のエントリーは、大都会岡山で生まれ、大都会岡山のコミュニティの方々を尊敬し、そして札幌で暮らしている(!) @tricknotes が書かせていただきます。
ぼくの記事ではタイトルの通り、速度最適化された js コードについて見ていきたいと思います。
はじめに
「その書き方だと遅いから」「こっちの方が速いよ」なんていう言葉をちょくちょく耳にする機会があります。
でもそれって本当でしょうか?
コードの実行速度は処理系の実装と密に関係していて、パフォーマンス計測なしには語ることができません。
また、そのコードの実行のされ方によっても実行速度は大きく変わってくるでしょう。
(たとえば、実行時のホットスポットがどこになるかというのは、引数やオブジェクトの状態などに左右される場合があります。)
そこで今回は、 Node.js の EventEmitter を読んで、 JavaScript の速度最適化について考えてみることにしましょう。
Node.js を選んだ理由
Node.js はイベント駆動の非同期スタイルを強制していることで知られています。そして、このイベント駆動のインターフェースを提供しているモジュールが EventEmitter です。
Stream や http.Server など多くの主要なモジュールがこの EventEmitter を継承することで、イベント駆動のインターフェースをユーザに提供しています。
(参考)
ということは、 EventEmitter 内でパフォーマンスの悪い箇所があれば、Node.js 全体のパフォーマンスに大きな影響を与えることになります。
実際に、この EventEmitter の中では多くの速度最適化が施されており、これが Node.js 全体の実行速度を維持するための重要なポイントとなっています。
(イベント名に __proto__
など、特定の名前が利用できないという不具合がありますが、それよりもスピードの方が重要であるという方針とのことです joyent/node#4366)
ではさっそく、この EventEmitter 中からいくつかコードを例に挙げながら、実際に速度を計測して本当に速度最適化されていることを確認していきましょう。
EventEmitter は events.js の中で定義されています。
本エントリーでの対象バージョンは Node.js 0.9.4-pre (9f4c0988) とします。
$ node -v v0.9.4-pre $ node > process.versions { http_parser: '1.0', node: '0.9.4-pre', v8: '3.13.7.4', ares: '1.9.0-DEV', uv: '0.9', zlib: '1.2.3', openssl: '1.0.1c' }
最適化コードについて
○ Function#apply より Function#call
Function オブジェクトを、任意のオブジェクトをコンテキストとして実行するためのメソッドとして Function#apply と Function#call があります。
多くの処理系の場合と同じく、Node.js の場合もこの両者の間には大きな速度の差あるようです。
そのため、極力 Function#apply を使わないようなコードになっています。
だいぶ濃い感じになっていますね。
普通に書くと、以下のように1行で済むはずです。
handler.apply(this, Array.prototype.slice.call(arguments, 1));
実際、両者の間にどれくらいの速度差があるのか測定してみましょう。
// Function#apply と Function#call の比較 var count = 10000000; var now, i; var obj = {}; var key = 'key'; var fn = function() {}; now = Date.now(); for (i = 0; i < count; i++) { fn.apply(this, [1]); } console.log('* apply: %d ms', Date.now() - now); now = Date.now(); for (i = 0; i < count; i++) { fn.call(this, 1); } console.log('* call: %d ms', Date.now() - now);
* apply: 519 ms * call: 160 ms
たしかに Function#apply の方が遅いようですね。
○ delete より null
オブジェクトの特定のキーに紐付く値を削除する場合、値だけを削除する方法(null や undefined を代入する方法)とキーごと削除する方法(delete キーワードを使う方法)があります。Object#hasOwnProperty などを利用せず、値の truthy/falsy のチェックだけを行うのであれば、この両者の間で大きな動作の違いはありません。
ただ、Node.js の場合は前者の方が圧倒的に高速に動作します。
そのため、不要になったオブジェクトを破棄する際には、delete を利用せず null を代入するようになっています。
EventEmitter の中では、 EventEmitter#removeListener と EventEmitter#removeAllListeners の中で使われています。
// delete と null を代入の比較 var count = 1000000; var now, i; var obj = {}; var key = 'key'; now = Date.now(); for (i = 0; i < count; i++) { obj[key] = 1; obj[key] = null; } console.log('* obj[key] = null: %d ms', Date.now() - now); now = Date.now(); for (i = 0; i < count; i++) { obj[key] = 1; delete obj[key]; } console.log('* delete obj[key]: %d ms', Date.now() - now);
* obj[key] = null: 21 ms * delete obj[key]: 405 ms
速度が処理系に依存することの参考までに、このベンチマークを FireFox 16.0.2 (Max OS X) で実行してみました。
すると、以下のような結果となりました。
* obj[key] = null: 5520 ms * delete obj[key]: 5857 ms
6%程度の差なのでそこまで大きな差ではありません。
この結果から、速度は処理系に依存していることがわかります。
○ Array#slice より for 文 & 代入
配列(もしくは arguments) を部分的に切り出したい場合、Array#slice を利用するのが簡単です。
ただ、Node.js では Array#slice を呼ばないように、 Array を要素数指定で初期化して、ひとつづつ値を詰めている箇所があります。
- https://github.com/joyent/node/blob/9f4c0988c37b9df60e45c26c25c91e45757d8f62/lib/events.js#L102-L103
ここを簡単に記述するなら以下のようになります。
Array.prototype.slice.call(arguments, 1);
これについてはどの程度パフォーマンスに影響が出ているのでしょうか?
// Array#slice(1) と new Array() + for についての比較 var count = 10000000; var now, i; var obj = {}; var key = 'key'; var array = ['type', 1, 2, 3 /* any arguments */]; now = Date.now(); for (i = 0; i < count; i++) { array.slice(1); } console.log('* Array#slice(1): %d ms', Date.now() - now); now = Date.now(); for (i = 0; i < count; i++) { var l = array.length; var _array = new Array(); for (var j = 1; j < l; j++) { _array[j - 1] = array[j]; } } console.log('* new Array(): %d ms', Date.now() - now); now = Date.now(); for (i = 0; i < count; i++) { var l = array.length; var _array = new Array(l - 1); for (var j = 1; j < l; j++) { _array[j - 1] = array[j]; } } console.log('* new Array(length): %d ms', Date.now() - now);
* Array#slice(1): 837 ms * new Array(): 388 ms * new Array(length): 267 ms
以上のことより、Array#slice を使った場合とそうでない場合では、速度差があることがわかります。
補足
ここまででは書いていませんでしたが、部分的にコードを取り出しただけのベンチマークでは不十分なケースもあります。
コードの書き方だけではなく、文脈に沿って最適化が行われる場合があるので、ちゃんとプロダクションコードに対してベンチーマークをとって全体で動かしてみるまで、速度最適化されたかどうかは判定できません。
(ということを @koichik さんに教えてもらいました。勉強になります!)
@tricknotes 手抜きしてこちらで。すでにマージされた GitHub の PR (4393) ですが、ベンチマークを載せるなら修正前後の emit() 呼び出しで比較すべきだったと思います。最適化の効き具合による影響があるためです。(続く)
2012-12-10 23:03:59 via TweetDeck to @tricknotes
@tricknotes (続き) 実際、昨年 arguments を使わないようにしたところ、emit() のパフォーマンスは劣化しました。現在の V8 とはバージョンが違うので今では当てはまらないかもしれませんが、あのベンチマークでは確認にならないと思います。
2012-12-10 23:05:25 via TweetDeck to @tricknotes
結論
ここまで見てきてみなさまお分かりでしょうが、速度に最適化することと人間に最適化することが共存できるとは限りません。速度に最適化し過ぎてリーダブルではないコードになってしまうと、メンテナンス性を著しく下げてしまうことでしょう。しかし、ここまでで見てきた EventEmitter の例のように、可読性を捨ててでも速度に最適化すべき場面というのも確かに存在します。
もちろんその場合には、プロジェクトの性質によってどこまで速度最適化をするかということを判断する必要があると思います。
また、本エントリーの内容は、Node.js 0.9.4-pre (9f4c0988) での挙動です。
今後、同じコードで同じ最適化の効果が得られると保証されているわけではありません。
(関連)
みなさまのアプリケーションでも、もしパフォーマンスが問題であると感じたときは、ホットスポットがどこにあるか計測しつつ、どこまで可読性を犠牲にするかということを考えながらチューニングしていくことをオススメします。
まとめ
以上でぼくの記事は終わりとさせていただきます。
次回の大都会岡山 Advent Calendar 2012 は sutorada さんです。
引き続き、大都会岡山 Advent Calendar 2012 をお楽しみください:-)
SaCSS vol.40 で「ブラウザサイド MVC 入門」というタイトルで発表してきました
ブラウザの中での MVC を紹介する際に TODO アプリがよく例にあげられるかと思いますが、今回ぼくの発表ではその TODO アプリの手前までを丁寧に説明しました。
「なんでMVCが必要とされているのか」であったり、「解決する問題領域は何か」といったあたりを詳しく見ていく、という内容です。
概要はこんな感じ: (SaCSS vol.40 より)
ブラウザサイド MVC 入門画面遷移を行わず、1枚の html 上で対話的に操作するような web アプリケーションを目にする機会が増えてきました。
そんなアプリケーションを開発する際、jQuery 主体だったこれまでの開発スタイルだと画面表示と機能が切り離せなくなってしまい、変更に弱い作りになってしまうことになるでしょう。
この問題に対処するにはどうすればよいでしょう?
そんなときは先人の知恵を参考に、その解決策を探してみるのはいかがでしょうか。
ユーザからのインタラクションを受け付けるアプリケーションを上手く構築していく際のやり方として、MVC パターンが広く知られています。
本セッションでは、ブラウザ上での JavaScript の MVC の原点を辿り、その実装のひとつである Backbone.js を例にとって、整理されたコードについて考えてみたいと思います。
ウェブデザイナーさんが多かったのですが、だいぶ置いてけぼりにしてしまった感があって大変申し訳なかったなぁ、というのが反省:-<
今回、声をかけてくれた @h2ham に感謝!
ブラウザ用に書かれた mocha のテストを Node.js で動作させる mocha-ci-driver を作ってみました
JavaScript のテストを作成する際、動作環境を意識したコードを書くことを手間に感じる方は多いかと思います。
そこで今回は、ブラウザ用に作成した JavaScript のテストコードを、 Node.js を利用した CI 環境でも同じように動作させることができるツールとして、 mocha-ci-driver を作ってみたのでご紹介したいと思います。
* ブラウザでのテスト
本来、ブラウザ用に書かれたテストは基本的にはブラウザでしか動作しません。
受け入れレベルのテストを selenium などを利用して動作させるというのはよくある手段ですが、モデルのみのテストだとなかなかそうもいきません。
そのため、JavaScript のテストをすべて CI に組み込んで動作させることは困難かと思います。
ひとつのアプローチとして、ブラウザでもサーバ(今回は Node.js を対象としています)でも動作するようなコードに書き換えるというのも一つの手段かもしれません。
ロジックだけのモデル層のテストなら互換性を考慮するのも可能でしょう。
(function(global) { // code })( 'undefined' === typeof exports ? window : module.exports );
ただ、外部のライブラリや DOM に依存している部分についてはなかなか素直にはいきません。
例えば、 underscore や jQuery を利用していたら、その部分をブラウザ/サーバ用に差し替えるようなコードが必要になってくるでしょう。
(function(global, _) { // code })( // 環境依存を解消するためのコード(本質的ではない) 'undefined' === typeof window ? module.exports : window, 'undefined' === typeof _ ? require('underscore') : _ );
この対応というのは、本質的なコードではない上にテストの信頼性も損なってしまいます。
またこの問題を解消できたとしても、DOM に依存するコードは単独でテストをしづらいという課題が残ります。
ブラウザで動作しているテストコードを、特別な変更無しで CI 環境でも動作させることができたら素敵ですよね。
というわけで、作ってみました。
* mocha-ci-driver を使うと
mocha-ci-driver は mocha で書かれているブラウザ用のテストを、Node.js 上でも動作させるためのドライバです。
これを使うと、ブラウザ用に書かれたテストを CI に組み込めます。
なので、普段の開発では CI で動作させておいて、ブラウザ互換を確認したいときは各ブラウザでテストを実行することができるようになります。
Node.js 用の設定はこんな感じです。
1. まず、ドライバを実行するためのファイルを追加します。
(値は各環境によって適宜変更してください。)
// ./test/driver.js var Driver = require('mocha-ci-driver').Driver , basedir = __dirname + '/../' , port = 8080 , testHtml = '/test/index.html' , driver = new Driver(basedir, port) driver.run(testHtml);
2. 次に、普段利用しているテスト用の html を一部修正し、
html の中でなくドライバ側でテストを実行するよう設定します。
(mocha はテストを実行した後はテスト内容を捨ててしまうので、この設定が無いと、ドライバ側で実行すべきテストを取得できなくなってしまいます。)
(before)
<script> $(function () { mocha.run(); }); </script>
(after)
<script> $(function () { // Node.js で実行時にはこのタイミングでテストを実行しない if (!/Node.js/.test(navigator.appName)) { mocha.run(); } }); </script>
3. この設定が完了すれば、あとは Node.js で実行するだけです。
$ node ./test/driver.js
* mocha の対応バージョン
ただ、この mocha-ci-driver が対応しているのは、 visionmedia/mocha@a186b8dba1 以降になります。
2012.03.04 現在、 tag は切られていないので、 mocha-ci-driver を利用するためにはリポジトリから mocha を取得してきて make する必要があります。
$ git clone git://github.com/visionmedia/mocha.git mocha
$ cd mocha
$ make clean && make
これで mocha.js がビルドされるので、この mocha.js をテスト用の html で利用するようにしてください。
というわけで、 ブラウザでもサーバでも動作するテストに興味がある方は mocha-ci-driver 試してみてください:-)