そんなの知ってるよという人も多いですが、勘違いしている人も多いようです。
使用している人が多いであろうjQueryには、配列やオブジェクトを簡単にループ処理できる jQuery.each()
がありますが、このメソッドはオブジェクトの場合は for...in
文で処理しています。
プロトタイプチェーンのプロパティまで列挙される jQuery.each()
まず、jQuery.each()
がどのように書かれているかのぞいてみます。
// jQuery 3.1.1 から一部抜粋
jQuery.extend( {
// 〜〜〜〜
each: function( obj, callback ) {
var length, i = 0;
if ( isArrayLike( obj ) ) {
length = obj.length;
for ( ; i < length; i++ ) {
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
break;
}
}
} else {
for ( i in obj ) {
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
break;
}
}
}
return obj;
},
// 〜〜〜〜
} );
お気づきかと思いますが、for...in
文に Object.prototype.hasOwnProperty
が使われていません。Object.prototype
等が 拡張 汚染されていると列挙されてしまう事になります。
// Object.prototype に適当なプロパティを追加
Object.prototype.pinya = 'ぴにゃー';
// これをループ処理させたい
var idol = {
cute: '島村卯月',
cool: '渋谷凛',
passion: '本田未央',
};
for (var type in idol) {
console.log(type + ': ' + idol[type]);
}
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
// "pinya: ぴにゃー"
$.each(idol, function (type, value) {
console.log(type + ': ' + value);
});
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
// "pinya: ぴにゃー"
// 両方とも余計なものが入ってしまった!
これはたいへんだ!
それなら jQuery.each() でも hasOwnProperty でチェックしよう(!?)
余計なものまで列挙されてしまうのなら jQuery.each()
でも Object.prototype.hasOwnProperty
を使えばいいのでは?(!?)
Object.prototype.pinya = 'ぴにゃー';
var idol = {
cute: '島村卯月',
cool: '渋谷凛',
passion: '本田未央',
};
$.each(idol, function (type, value) {
if (idol.hasOwnProperty(type)) {
console.log(type + ': ' + value);
}
});
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
無駄なコードだ……
なぜ jQuery.each() は hasOwnProperty を使っていないのか
初期状態の Object.prototype
のプロパティ達は列挙されないように定義されています。なので、Object.prototype.hasOwnProperty
は不要なのです。
そもそも、Object.prototype
は 拡張 汚染するべきではないと散々いわれまくっていますし、どうしても 拡張 汚染したいのであれば列挙されないように定義してやる事が重要です。
prototype 拡張 汚染の事まで考慮するのは正直面倒であり、処理の高速化にも不向き(hasOwnProperty
は関数なので処理の手間が増える)ということで、jQueryは Object.prototype
は初期状態である(または列挙されないように定義されている)という前提で処理を行っているようです。
「どうしても Object.prototype を拡張したいんだ!」
そんな人がいるかどうかはさておき、for...in
で列挙されないように定義することは可能です。Object.defineProperty()
や Object.defineProperties()
を使います。
Object.defineProperty() - JavaScript | MDN
Object.defineProperties() - JavaScript | MDN
// Object.prototype に適当なプロパティを追加
Object.defineProperty(Object.prototype, 'pinya', {
value: 'ぴにゃー',
});
// これをループ処理させたい
var idol = {
cute: '島村卯月',
cool: '渋谷凛',
passion: '本田未央',
};
for (var type in idol) {
console.log(type + ': ' + idol[type]);
}
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
$.each(idol, function (type, value) {
console.log(type + ': ' + value);
});
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
しかし汚染している事には変わりはないので、扱いには十分気を付けた方がよさそうです。
あれ? つまり for...in で十分では?
jQuery.each()
は Object.prototype.hasOwnProperty
を使っていないのはわかりました。
ということは、毎回関数を実行する jQuery.each()
で処理しなくても普通に for...in
使えばもっとコードがシンプルかつ処理速度も速いということになります。
すごく簡単に計測してみます。
- ブラウザ:Chrome 56.0 (64-bit), Firefox 52.0 (32-bit)
- jQuery:v3.1.1
var obj = {}, i = 0;
// 1万個のプロパティがあるオブジェクトを作る
for (; i < 10000; i++) {
obj[i] = i;
}
// for...in
function forInTest() {
var dummy;
console.time('for...in');
for (var key in obj) {
dummy = key + ', ' +obj[key];
}
console.timeEnd('for...in');
}
// jQuery.each()
function jQueryEachTest() {
var dummy;
console.time('jQuery.each');
$.each(obj, function (key, value) {
dummy = key + ', ' + value;
});
console.timeEnd('jQuery.each');
}
結果
Chrome 56.0
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 | |
---|---|---|---|---|---|---|
for...in | 0.362ms | 0.389ms | 0.336ms | 0.358ms | 0.328ms | 0.335ms |
jQuery.each() | 0.760ms | 0.747ms | 0.761ms | 0.757ms | 0.772ms | 0.759ms |
Firefox 52.0
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 | |
---|---|---|---|---|---|---|
for...in | 5.01ms | 5.04ms | 4.90ms | 4.70ms | 4.77ms | 4.884ms |
jQuery.each() | 5.17ms | 5.31ms | 5.14ms | 4.91ms | 5.00ms | 5.106ms |
(Firefoxおそっ)
for...in
の方が速いのがわかります。
Object.keys() を使ってループ処理させる方法もある
処理速度を気にする必要があまりないのであれば、Object.keys()
を使うのもいいかと思います。
Object.keys() - JavaScript | MDN
Object.keys()
は、そのオブジェクト自身の列挙可能なプロパティを配列にするメソッドです。これならプロトタイプチェーンのプロパティの事は考えなくても大丈夫です。
// これをループ処理させたい
var idol = {
cute: '島村卯月',
cool: '渋谷凛',
passion: '本田未央',
};
// Object.keys() でプロパティの配列を取得する
var keys = Object.keys(idol);
console.log(keys);
// ["cute", "cool", "passion"]
keys.forEach(function (type) {
console.log(type + ': ' + idol[type]);
});
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
$.each(keys, function (i, type) {
console.log(type + ': ' + idol[type]);
});
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
/* ES2015 の for...of も使える */
for (let type of keys) {
console.log(type + ': ' + idol[type]);
}
// "cute: 島村卯月"
// "cool: 渋谷凛"
// "passion: 本田未央"
いろいろ応用が利きそうです。
おわりに
こんな事を書くことになったきっかけは、「for...in
はプロトタイプチェーンのプロパティまで列挙されるから hasOwnProperty
でチェックするか jQuery.each()
を使おう」といった感じのものが見られたからです。
JavaScriptは進化し続けているので難しいですね……