はじめに
JavaScriptの this
は割と厄介だけど便利でもある存在ですが、この this
に文字列や数値が入る場合、知らないと「あれ?」ってなる現象が起こります。
数値や文字列が 'object'
判定になる!?
jQueryの $.each()
で配列やオブジェクトをループさせることはよくある事かと思います。
このメソッドでは this
は配列やオブジェクトの値が入っているわけですが……
var arr = [123, NaN, Infinity, 'abc'];
$.each(arr, function (i) {
var type = typeof this;
console.log(i + ': ' + this + ' is ' + type);
});
// 0: 123 is object
// 1: NaN is object
// 2: Infinity is object
// 3: abc is object
あれ? typeof
で調べたら全部 'object'
になってしまいました。
他にも、例えば Function.prototype.call()
や Function.prototype.bind()
で this
に値を入れても同じ結果になります。
function typeOfThis() {
return typeof this;
}
typeOfThis.call(123); // 'object'
typeOfThis.call(NaN); // 'object'
typeOfThis.call(Infinity); // 'object'
typeOfThis.call('abc'); // 'object'
typeOfThis.bind(123)(); // 'object'
this
の値を見てみる
普通に計算にしたり文字列結合できたりするのに、なぜ typeof
で調べると 'object'
なのか。
console.dir()
で this
がどうなっているか見てみます。
function thisTest() {
console.dir(this);
}
thisTest.call(123);
/*
Number
__proto__: Number
constructor: function Number()
~省略~
[[PrimitiveValue]]: 123
*/
thisTest.call(NaN);
/*
Number
__proto__: Number
constructor: function Number()
~省略~
[[PrimitiveValue]]: NaN
*/
thisTest.call(Infinity);
/*
Number
__proto__: Number
constructor: function Number()
~省略~
[[PrimitiveValue]]: Infinity
*/
thisTest.call('abc');
/*
String
0: "a"
1: "b"
2: "c"
length: 3
__proto__: String
constructor: function String()
~省略~
[[PrimitiveValue]]: "abc"
*/
よくわからない部分もあるかと思いますが、数値リテラルや文字列リテラルではなく数値オブジェクトや文字列オブジェクトとして this
に入っているのがわかります。
要するに、new Number(123)
や new String('abc')
等で生成したのと同じものになります。だから typeof
では 'object'
になってしまうんですね。
this
にはオブジェクトが入るべきという感じでしょうか。(実はよく知らないですごめんなさい)
こうすれば解決できる
じゃあどうすればいいか、こういった方法があります。
jQueryであれば第2引数を使う
$.each()
のコールバック関数の第2引数にも配列やオブジェクトの値を取ることができます。
var arr = [123, NaN, Infinity, 'abc'];
$.each(arr, function (i, value) {
var type = typeof value;
console.log(i + ': ' + value + ' is ' + type);
});
// 0: 123 is number
// 1: NaN is number
// 2: Infinity is number
// 3: abc is string
this
で扱うよりも自然な感じがしてわかりやすいので、こちらの使い方の方が個人的にはいいのではと思います。
Strictモードにしてしまう
'use strict';
をコードの頭に置くことにより、ブラウザがそのコードをStrictモードとして扱います。
Strictモードは厳格な処理を行い、this
にはそのままの型で入るようになります。
var arr = [123, NaN, Infinity, 'abc'];
$.each(arr, function (i) {
'use strict';
var type = typeof this;
console.log(i + ': ' + this + ' is ' + type);
});
// 0: 123 is number
// 1: NaN is number
// 2: Infinity is number
// 3: abc is string
Function.prototype.call()
でも同様に。
function typeOfThis() {
'use strict';
return typeof this;
}
typeOfThis.call(123); // 'number'
typeOfThis.call(NaN); // 'number'
typeOfThis.call(Infinity); // 'number'
typeOfThis.call('abc'); // 'string'
ただ、Strictモードに対応しているブラウザでないと意味がありません。
素直に引数として渡した方が確実な気もします……
普通に引数として渡す
ぶっちゃけ、this
に入れる必要がないのであれば、引数として扱うべきです。
function typeOf(obj) {
return typeof obj;
}
typeOf(123); // 'number'
typeOf(NaN); // 'number'
typeOf(Infinity); // 'number'
typeOf('abc'); // 'string'
おわりに
会社の同僚がこれの沼にどはまりしていたので、やはり知らない人もいるのだと思いメモとして書きました。
jQueryに限った事ではないですが、this
を取り扱う場合はしっかりと把握しておく必要がありますね。