jQuery のバグを見つけてから修正されるまで ― 2013年01月28日 02時17分
1 月 24 日に開催された Kyoto.js meetup 4 で「jQuery のバグを見つけてから修正されるまで」と題した発表を行いました。
jQuery へのコミットに関して 2 行でまとめるとすれば次のようになるでしょうか。
- jQuery のソースコードはショートコーディングの嵐なので心してかかる
- Contributing to jQuery と jQuery Core Style Guidelines は必読
発表の筋書きは以下の通りです。
jQuery のバグを見つけてから修正されるまで
フォロー・ミー
- nanto_vi (TOYAMA Nao)
- 株式会社はてな アプリケーションエンジニア
- クライアントサイド (JavaScript)
- サーバーサイド (Perl)
- jQuery をバリバリ使っている
- Deferred
- イベント
- DOM 操作
ある日どこかで
街中にクリスマスの装飾が灯り始めるころ
うんうん、変わったよね
…
……
そういえば Deferred を使ったとき this の値はどうなるんだったっけ?
$.Deferred().done(function () {
this // ← ココとか
}).then().done(function () {
this // ← ココの値は?
});
jQuery 1.8.3 と jQuery 1.9 Beta 1 で結果が違う!
汚れなき悪戯
- #11405 (deferred.notify() invokes progressCallbacks with deferred as context) -- jQuery Core - Bug Tracker
- Deferred: .resolve(), .reject() and .notify() now set the callback conte... · 23d7cf0 · jquery/jquery
ふむふむ…… (コードを読む)
ふむふむ…… (コードを読む)
ふむふむ…… あれ?
これ、自分の書いたコードが動かなくなる?
$.Deferred().then(function () {
// ここでの this が
return $.Deferred().resolveWith(this, arguments);
}).done(function () {
// ここにも引き継がれてほしい
this
});
……
まずい!
バック・トゥ・ザ・フューチャー
- https://github.com/jquery/jquery/blob/1.8.3/src/callbacks.js
- https://github.com/jquery/jquery/blob/1.8.3/src/deferred.js
- https://github.com/jquery/jquery/blob/1.9.0b1/src/callbacks.js
- https://github.com/jquery/jquery/blob/1.9.0b1/src/deferred.js
どこを直せば自分のコードが動くのか
コードとにらめっこ
にらめっこ
にらめっこ
……
問題なのはここ、でもここを変えると先の変更の意味が失われるから……
こことここか!
羊たちの沈黙
直すならテストを書かないと
テストを書くならテストを走らせないと
テストを走らせるなら jQuery のビルド環境を作らないと
grunt? Node.js なら入ってるし楽勝でしょ
……
うわあ、この Node.js バージョンが古い!
うわあ、この OS バージョンが古い!
うわあ、この Python (ry
……
沈黙
そしてクリスマスが過ぎ、正月が過ぎた
風と共に去りぬ
やっぱり正月松の内は休まないとね
ってなになに……
げげー、1.9-stable ブランチ!
もはや一刻の猶予も許されない
禁じられた遊び
俺にはこの SpiderMonkey JavaScript Shell がある!
秘技、テストのコピペ改変!
荒野の用心棒
「これ直さへんとあかんちゃう?」
J 「プルリクエストにしてーな」
「プルレクったでー」
D 「ここ冗長やろ。もっと削れるやろ」
(そない言われても…… これ以上どう切り詰めろいうんや……)
(しゃあないな、この条件分岐をもっと前に持ってきて……)
(あれ、この変数、フラグに流用できるちゃうか……)
「どや!」
D 「まあええんちゃうか」
素晴らしき哉、人生!
D 「修正取り込んだで。おーきに」
J 「僕と契約して貢献者になってよ!」
めでたしめでたし
※ この物語は実話を基にしたフィクションです。
jQuery で HTTP 接続するときの書き方 ― 2012年12月16日 19時41分
12 月 13 日に Kyoto.js の第 3 回 meetup で、「jQuery で HTTP 接続するときの書き方」と題した 5 分間のライトニングトークを行いました。以下にその内容を一部再構成して収録します。
こんにちは、nanto_vi です。今日は jQuery で HTTP 接続をするときの書き方について話します。
皆さん jQuery を使うことも多いかと思います。jQuery で HTTP 接続をするとき、古いサンプルだと次のような書き方が載っています。
$.ajax({
url: '/foo/bar',
data: { baz: 'qux' },
success: function (data) {
console.log(data);
},
});
接続完了時の処理をコールバック関数として $.ajax() に渡してやる形ですね。しかし、現在この書き方は非推奨となっており、替わりに次のようにも書きます。
$.ajax({
url: '/foo/bar',
data: { baz: 'qux' },
}).done(function (data) {
console.log(data);
});
$.ajax() の返り値に対して、done メソッドで接続完了後の処理を登録する形です。この書き方の何がいいかといえば、返り値を使いまわしたり done メソッドを複数回呼び出したりして、完了後の処理を後から追加できるところです。昔の書き方では完了後の処理がコールバック関数として $.ajax() の内部に格納されていましたが、現在は「完了後の処理」だけを $.ajax() の外部に (「プロミス」として) 取り出すことが可能になったわけですね。
今「内部の処理を外部に取り出す」と言いましたが、この言葉はどこかで聞き覚えがありませんか。そう、内部イテレータと外部イテレータです。
内部イテレータは、オブジェクトが個々の要素に対する処理を受け取り、オブジェクト内部で自身の各要素に適用させる形、
// 内部イテレータの使用例
$('p').each(function () {
doSomethingWith(this);
});
外部イテレータは、オブジェクトから「各要素を列挙する」という機能だけをオブジェクト外部に取り出す形です。
// 外部イテレータの実装例と使用例
$.fn.iterator = function () {
var i = 0, n = this.length, self = this;
return {
hasNext: function () { return i < n; },
next: function () { return self[i++]; }
};
};
var iterator = $('p').iterator();
while (iterator.hasNext()) {
var element = iterator.next();
doSomethingWith(element);
}
JavaScript 1.7 以降ではジェネレータという機能により、内部イテレータのような書き方で外部イテレータを生成することができます。
さて、「処理を内部に持ってしまっている」というのは、イベントハンドラの登録も同じですね。
$(document).on('click', function (event) {
...
});
イベント発生時の処理をコールバック関数として jQuery オブジェクト内部に渡していますが、これを外部に持ち出すことはできないのでしょうか。
実はそれを可能にするものとして Reactive Extensions (Rx) があります。Rx 入門記事の図にもあるように、Rx を使うと空間にまたがる要素 (配列など) の列挙と時間にまたがる要素 (イベントなど) の列挙を統一的に扱うことができます。
Reactive Extensions は主に .NET Framework 上で利用されていますが、JavaScript での実装として RxJS が公開されています。
- neue cc - linq.js & Reactive Extensions for JavaScript(RxJS)入門
- neue cc - Reactive Extensions入門 + メソッド早見解説表
- Rx (Reactive Extensions) - Home
ObserverパターンとIteratorパターンは同じだったんだよ!なんだってー!
そう、ObserverパターンとIteratorパターンは同じなのだよ、ナンダッテー!
(このあたりで時間切れ)
はてなでコードを書くときに気をつけていること ― 2011年12月18日 14時37分
こんにちは、はてなでアプリケーションエンジニアをしている nanto_vi です。この記事は Hatena::Staff Advent Calendar 2011 の一環として書いています。Advent Calendar の会場ははてなブログとなっていますが、主催の antipop さんに尋ねたところ何でもよいというような返答があったのでここで書きます。
はじめに謝っておきますが、この記事ははてなとほとんど関係がありません。タイトルに「はてな」と入っているのは Advent Calendar の要件を満たすための目くらましあり、内容はといえばはてなのノウハウでもなんでもない、私が最近個人的に心がけていることです。ごめんなさい。
できれば明示的に
業務で書くコードは複数人により読み書きされメンテナンスされるので、わかりやすさが重要になってきます。わかりやすさを保つためには暗黙的に行われる操作を減らし、明示的にコードに表した方がよいと感じます。たとえば、Perl では、
use Encode;
# encode_utf8 関数は Encode モジュールで定義されている
my $octets = encode_utf8 $string;
のようにモジュール読み込み時に自動的に関数をインポートすることができますが、それよりも、
use Encode qw/encode_utf8/;
my $octets = encode_utf8 $string;
のようにインポートする関数を明示的に指定したほうが、その関数がどこで定義されているのかわかりやすいでしょう。
個人的には、関数や変数の名前で grep をかけてその定義(外部で定義されている場合は宣言)を探し出せることが、「明示的」であるための最低線ではないかと思います。
入力には正規化を、出力には符号化を
アプリケーションを作るうえで入出力の扱いは避けて通れません。入力元はユーザーだったりバッチ処理スクリプトの引数だったり、出力先はユーザーだったりデータベースサーバーだったりしますが、いずれにおいても入力には正規化処理を、出力には符号化処理を、それぞれ施すことになります。
正規化は一般に不可逆です。空白文字類をすべてスペースに置換した後はもともとスペース以外の空白文字が含まれていたのかわかりませんし、数値「42」を生み出した入力は "42" でも "42.0" でも、はたまた "0x2A" でもありえます。
符号化は一般に可逆です。あるデータをユーザーのもとへ送り届けるのに、そのデータを HTML のテキストとして扱えるよう一部文字を文字参照の形に符号化し、その HTML ソース全体を文字符号化方式 UTF-8 をもって符号化し、さらには HTTP メッセージに、TCP のセッションに、IP のパケットにと符号化を重ねていきますが、それらすべては符号化規則を逆に適用することで元の形式へ復元できます。
たとえば HTML として解析され、その結果が JavaScript の文字列として、HTML として、URI としてと順に解析されるデータを Template-Toolkit で出力する場合、解析の逆順でそれぞの形式に応じた符号化を施す必要があります。
<script type="text/javascript">
document.write('<a href="/search?q=[% word | uri | html | js %]">検索<\/a>');
</script>
script 要素の内容は CDATA 型であり文字参照は解決されないので、最後に html フィルタをかけることはしません。また、そもそもこのような多重にフィルタをかける事態は避けたほうが賢明でしょう。
入出力いずれにおいても、データがどういう形式で扱われるのかを意識し、それに応じた正規化処理または符号化処理を施すことで、意図しない入力が致命的な挙動を引き起こすこともある程度は防げるのではないかと考えます。
IE 6/7 で文書間通信を実現するための一案 ― 2011年12月08日 23時32分
HTML5-WEST.jp 飲み会 UST というのがあるそうで、「参戦希望者募集」とお誘いを受けたのですが、「参戦」というからには何かしら戦の準備を整えねばなるまいと、以前から考えていたことを夜なべして実装しました。
いわゆる HTML5 の文書間通信、window.postMessage() を IE 6/7 でどう再現するかという話で、about:blank を指す隠しフレームを二つ用い、window.name を介することで双方向の通信を実現しています。
どうも IE での about:blank は、その空白ページを読み込ませた文書の生成元を継承するらしく、たとえば http://example.org/ から location.href = 'about:blank'; を実行すれば http://example.org を生成元とする about:blank になります (少なくともそのように見えます)。これを利用し、一つのフレームに二つの文書から交互に about:blank を読み込ませあうことで、そのフレームの window.name を両文書で共有できるという仕組みです。
about:blank を読み込ませるのに location.reload() を使えば履歴に余分な項目を残すこともありませんし、フラグメント識別子を使った場合と違ってデータ量にもだいぶ余裕が出ます。
Template::Plugin::JSON::Escape 公開 ― 2011年06月15日 20時57分
Template::Plugin::JSON::Escape という Perl モジュールを公開しました。
Template Toolkit を使って HTML を出力するとき、HTML 中に JSON を埋め込みたいことがあります。そのためのモジュールとして Template::Plugin::JSON が存在するのですが、これは小なり記号 (<) などの文字を素通しするため、JSON の内容によっては不正な HTML が出力されてしまいます。
Template::Plugin::JSON::Escape は一部記号を \uxxxx の形式にエスケープするので、JSON の内容にかかわらず出力する HTML を妥当に保てます (もちろん、JSON 出力部以外が妥当な HTML を出力するのであれば、です。なお、SGML のコメント内に JSON を埋め込む場合はこの限りではありません)。副次作用として、XSS の防止にも一定の効果を期待できます。
また、Template::Plugin::JSON 0.06 は Moose を使っていますが、私が普段 Perl を使っている環境には Moose が入っていません。そのためだけに Moose を導入するのは難しいので、Moose を使用しないモジュールが求められていたというのもあります。
最近のコメント