Edited at

jQuery.each() は hasOwnProperty を使っていない

More than 1 year has passed since last update.

そんなの知ってるよという人も多いですが、勘違いしている人も多いようです。

使用している人が多いであろうjQueryには、配列やオブジェクトを簡単にループ処理できる jQuery.each() がありますが、このメソッドはオブジェクトの場合は for...in 文で処理しています。


プロトタイプチェーンのプロパティまで列挙される jQuery.each()

まず、jQuery.each() がどのように書かれているかのぞいてみます。


jquery-3.1.1.js

// 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は進化し続けているので難しいですね……