John Resig の忍者本(Secret of the JavaScript Ninja)に Kindle 版が追加された

jQuery を作った John Resig が執筆したJS本、 Secrets of JavaScript Ninja が今年あたまに発売しましたが、その Kindle 版(.mobiファイル)が本日(か昨日)出版された模様です。

JavaScript の書き方は時代によってトレンドのようなものがありますが、現在のトレンドは関数だそうですね。実際 JavaScript の関数はいわゆるクラスよりも柔軟で色々なことができます。色々できるだけに、頭の使い方にもコツが要ったりするのですが、その訓練のために「イマドキのJS」の先駆者たる氏の著書を読むのはいい頭の運動になると思うんですね。JS忍者の秘密と題された本書でその教えを紐解こうというわけです。

まだ邦訳はされていませんが、ご購入はこちらから。

本家のほうで、 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 と同じように、 durationeasing といったオプションも指定できる。

案外知られていないのではなかろうか。スムーススクロールなどに使えそうですね。

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 から、 animatefadeOut といった処理がキューに入る系のメソッドでは、キューがすべて空っぽになった時点で promise されていれば、 resolve が発動するようになっている。*1

とすると上コードは、以下のようにもかける。

$('.target')
    .animate({
        top: '+=100px'
    }, 500, 'swing')
    .promise().done(function () {
        // アニメーション完了後に実行される
        alert('done!');
    });

そして嬉しいことに、 promise する jQuery オブジェクトは、複数の要素が選択されていた場合、選択されているすべての要素のキューが消化されたタイミングで resolve が発動するみたい。

素直に jQuery の恩恵を受けておきましょう。

*1:こんな説明でいいのか……? このあたりの詳しい説明は他のリソースにお任せすることにする。

jQuery でアニメーションの前後に .css() とか .addClass() とかしたい時に .queue() を使う

やりたいこと

jQueryanimate メソッドを使って見た目を作っている時、アニメーションの完了後にクラスを付与したいとか、 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() メソッドを使う

そもそも、 jQueryanimate メソッドなどは、 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 は処理が終了したという合図になるため、記述しないとキューの処理がそこで止まったままになってしまう。