.each()の話
jQueryの.each()を使うと,JavaScriptのオブジェクトか配列を対象にした繰り返しを簡単に記述できる.*1
var list = [0,1,1,2,3,5,8,13]; $.each(list, function(index, elem){ console.log(elem * 2); }); // 0 VM2035:4 // 2 VM2035:4 // 2 VM2035:4 // 4 VM2035:4 // 6 VM2035:4 // 10 VM2035:4 // 16 VM2035:4 // 26 VM2035:4
で,このeachにはもう一つ書き方がある.
var list = [0,1,1,2,3,5,8,13]; $.each(list, function(){ // さっきは引数にindex, elemを指定していた console.log(this * 2); // 引数ではなく,thisでアクセスしている }); // 0 VM2032:4 // 2 VM2032:4 // 2 VM2032:4 // 4 VM2032:4 // 6 VM2032:4 // 10 VM2032:4 // 16 VM2032:4 // 26 VM2032:4
違いは?
ドキュメントにはこうある.
The value can also be accessed through the this keyword, but Javascript will always wrap the this value as an Object even if it is a simple string or number value.
http://api.jquery.com/jQuery.each/
値にはthisキーワードを使用してもアクセスできるが,Javascriptはそれが単なる文字列や数値であっても常にObjectとしてこの値をラップする.
jQueryのコードで言うとhttps://github.com/jquery/jquery/blob/master/src/core.js#L279のあたり.
これで特に大きな変化が起きるのが要素にnullやundefinedが含まれていた場合で,ブラウザで実行される場合windowとなる.*2
問題になるシナリオ
ある学校で,部活動とその部に所属している学生の名前を対話的に表示するウェブページを実装する場面を考える.
雑にはこういう感じ.
表示をスムーズに切り替えるため,このページではアクセス時に全学生のデータと,全部活動のデータをサーバから取得してる*3.それぞれの例を以下に示す.
var clubMembers = { 'cl001': ['st13010', 'st13011', 'st14012'], 'cl002': ['st13100', 'st14100', 'st14005'], 'cl003': ['st12023', 'st13045', 'st14009'] }; var students = { 'st13010': {'name': 'A', 'grade': 2, 'class': 'A', 'sex': 'F'}, ... 'st14009': {'name': 'I', 'grade': 1, 'class': 'A', 'sex': 'F'} };
名簿の表示について,ここでは先に詳細な学生リストをつくり,それを利用する方針で実装してみる.
var clubMembersDetail = function(clubName){ var detail = []; $.each(clubMembers[clubName], function(){ detail.push(students[this]); }); return detail; }; // ... $('#clubSelector').on('change', function () { var clubName = this.value; $.each(clubMembersDetail(clubName), function(){ $('ul#memberlist').append('<li>' + this + '</li>'); }); });
さて,ここでcl001のst14012が転校しており,すでに学生の名簿には無いとどうなるか.clubMembersDetailの3行目students[this]でundefinedとなるため,リストは[<student obj>, <student obj>, undefined]となり,表示は以下のようになる.
ここで,最後の空文字を出力している時のthisはwindowであり,つまりwindow.nameが""であることを出力している.window.nameは外部から書き換えが可能なため,システムの出力を信じてappendしている今回のケースではXSSにつながる.
ということで
最後は明らかに悪意のある例になってしまったけど*5,要素に値を入れるような場合では新たにwindowのプロパティを定義してしまったり,最悪の場合すでに定義されているwindowのプロパティを破壊することになるし,参照の例でもnullやundefinedを期待してcallbackの中でthis === nullやthis === undefinedのような条件を書いてしまうとうまく動かないことになる.
以上になります.お気をつけください.