John Resig の忍者本(Secret of the JavaScript Ninja)に Kindle 版が追加された
jQuery を作った John Resig が執筆したJS本、 Secrets of JavaScript Ninja が今年あたまに発売しましたが、その Kindle 版(.mobiファイル)が本日(か昨日)出版された模様です。
JavaScript の書き方は時代によってトレンドのようなものがありますが、現在のトレンドは関数だそうですね。実際 JavaScript の関数はいわゆるクラスよりも柔軟で色々なことができます。色々できるだけに、頭の使い方にもコツが要ったりするのですが、その訓練のために「イマドキのJS」の先駆者たる氏の著書を読むのはいい頭の運動になると思うんですね。JS忍者の秘密と題された本書でその教えを紐解こうというわけです。
まだ邦訳はされていませんが、ご購入はこちらから。
- Manning: Secrets of the JavaScript Ninja(本家)
- Amazon.co.jp: Secrets of the Javascript Ninja: John Resig(Amazon.com)
本家のほうで、 pBook + eBook と書いてあるやつが、実物の本と電子書籍がセットになったものです。 Amazon では実物しか売っていません。本家はちょっと割高ですが、今ならクーポンコードを入力することで 36% OFF で購入できます。クーポンコードは「UG367」で、入力方法などは JSer.info の記事が参考になりました。
発売もページ数もノビノビになってきた経緯がある本のようですが、それだけに期待も高まります。英語の本なんぞ読めるかと思っていましたが、この本なら頑張って読む気になるかなーと思って自分は買いました。
参考
忍者じゃなくてサムライじゃんね。
コンストラクタ関数内では return する変数の型によって返るモノが変わる
JavaScript で、(便宜上の)コンストラクタ関数を new
で呼ぶ時、関数内の途中で return
があると、その値が帰ると思っていた。これは半分は正しいのだが、半分は間違っていた。
たとえば以下は undefined
になるんで正しい。
var Hoge; Hoge = function () { this.aaa = 'hoge'; return {}; }; console.log(new Hoge().aaa); // undefined Hoge = function () { this.aaa = 'hoge'; return function () {}; }; console.log(new Hoge().aaa); // undefined
ところが以下は、コンストラクタ内での this
に当たるものがそのまま返されている。 Chrome で確認。
Hoge = function () { this.aaa = 'hoge'; return true; }; console.log(new Hoge().aaa); // hoge Hoge = function () { this.aaa = 'hoge'; return 1; }; console.log(new Hoge().aaa); // hoge Hoge = function () { this.aaa = 'hoge'; return 'aaa'; }; console.log(new Hoge().aaa); // hoge Hoge = function () { this.aaa = 'hoge'; return null; }; console.log(new Hoge().aaa); // hoge Hoge = function () { this.aaa = 'hoge'; return undefined; }; console.log(new Hoge().aaa); // hoge
どうやら、コンストラクタ内で作ったオブジェクトを return
した時にのみ、それが new
した元に返る模様。つまり new
すれば返り値はオブジェクトであると保証されているということか。
基本の基なのかもしれないけど、知らなかったのでメモった。
jQuery の .animate() で普通のオブジェクトをアニメーションさせる
説明するより見たほうが早かったりして。
$({ x:0, y:100 }) .animate({ x:100, y:50 }, { duration:5000, progress:function (anim, progress, fx) { console.log('x:' + anim.elem.x, 'y:' + anim.elem.y)); } })
このように、プリミティブなオブジェクトでも animate
メソッドでグリグリと値を変化させられる。通常の animate
と同じように、 duration
や easing
といったオプションも指定できる。
案外知られていないのではなかろうか。スムーススクロールなどに使えそうですね。
jQuery 1.7.x 以前の場合
jQuery 1.7.x 以前は、 animate
メソッドのオプションとして progress
を指定できない。その場合は step
を使用する。
$({ x:0, y:100 }) .animate({ x:100, y:50 }, { duration:5000, step:function (now, fx) { console.log(fx.prop, now); } })
注意点として、 step
に指定した関数は、アニメーションさせたいオブジェクトが持つプロパティの数ぶんだけ実行されるという点がある。たとえば上述のコードで、秒間20コマのアニメーションがあるとすると(jQuery.fx.interval == 50
)、5秒間で100コマのアニメーションとなる。 step
に指定した関数が呼ばれる回数はこれにプロパティの数を掛けた分になる。つまり200回。パフォーマンス的にはよろしくないかもしれない(未測定)。
jQuery で複数の要素のアニメーションが全て終わったら何かを実行する
jQuery でたくさんの要素をグリグリ動かしたいときに、完了時刻の違う複数の要素のアニメーションが両方終わった時に何かを実行する、というような処理をしたいと思うことがある。
完了時刻が遅い方のコールバックに処理を書けば、望みのことは出来る。ただ、このようにして書かれたコードは変更に弱いし、見ただけで何をしたいコードなのかがパッと見わからなくなる。
アニメーション完了後に何かをするといえば、慣例的に以下のように書くことがポピュラーである。
$('.target') .animate({ top: '+=100px' }, 500, 'swing', function () { // アニメーション完了後に実行される alert('done!'); });
jQuery 1.6 から、 animate
や fadeOut
といった処理がキューに入る系のメソッドでは、キューがすべて空っぽになった時点で promise
されていれば、 resolve
が発動するようになっている。*1
とすると上コードは、以下のようにもかける。
$('.target') .animate({ top: '+=100px' }, 500, 'swing') .promise().done(function () { // アニメーション完了後に実行される alert('done!'); });
そして嬉しいことに、 promise
する jQuery オブジェクトは、複数の要素が選択されていた場合、選択されているすべての要素のキューが消化されたタイミングで resolve
が発動するみたい。
素直に jQuery の恩恵を受けておきましょう。
jQuery でアニメーションの前後に .css() とか .addClass() とかしたい時に .queue() を使う
やりたいこと
jQuery の animate
メソッドを使って見た目を作っている時、アニメーションの完了後にクラスを付与したいとか、 CSS を変更したいというようなことはよくある。これをエレガントな感じに書きたい。
何も考えずに jQuery っぽく書いてみる
$('.target') .animate({ left: '100px', top: '100px' }, 500, 'swing') .css('background-color', 'red');
これだとうまくいかない。アニメーションが終了した時に背景色が赤になってほしいが、アニメーションが始まったと同時に赤くなってしまう。
animate
のコールバック関数を指定する
解決策のひとつとして、 animate
メソッドの最後の引数にコールバック関数を指定する方法がある。指定したコールバック関数は、アニメーションが完了した直後に実行されることになっている。
$('.target') .animate({ left: '100px', top: '100px' }, 500, 'swing', function () { // アニメーションが完了した後に実行される $(this).css('background-color', 'red'); });
正常に赤くなってくれた。これの問題点は、連なる動作が増えるとどんどん入れ子が深くなってしまうことだ。
$('.target').animate({ left: '100px', top: '100px' }, 500, 'swing', function() { $(this).css('background-color', 'red').animate({ top: '0' }, 500, 'swing', function() { $(this).css('background-color', 'green').animate({ 'left': '0' }, 500, 'swing', function() { $(this).css('background-color', 'blue'); }); }); });
$.Deferred
で連結する
かと言って、この程度の処理で Deferred の仕組みを使うのは少し冗長な感じがある……。
var tgt = $('.target'); $.when(tgt.animate({ left: '100px', top: '100px' }, 500, 'swing')) .then(function () { tgt.css('background-color', 'red'); }) .then(function () { return tgt.animate({ top: '0' }, 500, 'swing'); }) .then(function () { tgt.css('background-color', 'green'); }) .then(function () { return tgt.animate({ left: '0' }, 500, 'swing'); }) .then(function () { tgt.css('background-color', 'blue'); });
.queue()
メソッドを使う
そもそも、 jQuery の animate
メソッドなどは、 jQuery 内部のキューという順番待ちの仕組みで制御されている。この仕組みを使い、アニメーションが終わったら次のを実行して……。という順次処理を実現している。
したがって、このキューに任意の処理を追加出来ればいい。 jQuery にはそのためのメソッド queue
が用意されている。
queue
を使った書き方をすると以下の様なコードになる。
$('.target') .animate({ left:'100px', top:'100px' }, 500, 'swing') .queue(function () { $(this).css('background-color', 'red').dequeue(); }) .animate({ top:'0' }, 500, 'swing') .queue(function () { $(this).css('background-color', 'red').dequeue(); }) .animate({ left:'0' }, 500, 'swing') .queue(function () { $(this).css('background-color', 'red').dequeue(); });
注意点として、 queue
メソッドの引数に指定する関数内で、 $(this).dequeue()
をする必要がある。 dequeue
は処理が終了したという合図になるため、記述しないとキューの処理がそこで止まったままになってしまう。