このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

JavaScriptでハマった件:さすがにJavaScriptだな、ウム

「横道にそれすぎ」で騒いでいた「JavaScriptプログラムが動かない/解決できない」件。結局、僕の単純ミスでした。return [b];と書くべきところをreturn b;と書いていたのが原因でした。

ですが、この間違いがなかなか発見しにくかった事情が、いかにもJavaScriptらしいと笑ってしまいました(苦笑い)。

画面表示では区別できない値

まず、ブラウザのalertやRhinoのprintだと、次の3つの表示は区別できません。


alert(true);
alert([true]);
alert("true");

typeof演算子と組み合わせたり、nullでないオブジェクトならtoSource()メソッドを適用するなどしないと、[true]や"true"をtrueと誤解しかねません。次のコードで、最初の3つは同じ動作をしますが、後の3つは異なる動作をします。


if (true) alert("OK");
if ([true]) alert("OK");
if ("true") alert("OK");

if(!false) alert("OK");
if(![false]) alert("OK"); // do nothing
if(!"false") alert("OK"); // do nothing

[false]や"false"は、条件式内ではtrueとして評価されるからです。

親切で奇妙な型変換が仇<あだ>となる

僕がハマったケースではもう少し事情が複雑で、間違いがある関数badFuncから次のように値が流れていきます。


var x = badFunc();
var y = Array.prototype.concat.call(x, someArray);
var b = y[0];
if (b) {
// b が true のときの処理
}else {
// b が true でないときの処理
}

Array.prototype.concat.call(x, someArray) は、配列xと配列someArrayの連接です。Firefoxなら、Array.concat(x, someArray)と簡単に書けます(以下、簡単な記法を使います)。さて、xが配列だと僕は思い込んでいたのですが、実際のxは単なるブール値でした。JavaScriptではよくあることですが、こんなときエラーとはせずに、親切な型変換を自動的に行います。Array.concat(true, [2, 3]) でも Array.concat([true], [2, 3]) のように扱ってくれるのです。

ところが、なぜかこのとき、配列[true]に含まれるtrueはブール値(boolean)ではなくなるのです。new Boolean(true) で生成したオブジェクトに変換されます。そういうわけで、上のコードの変数bにはオブジェクトとしてのtrueが入ります。

ブール値(boolean)としてのtrueと、オブジェクト(object)としてのtrueは、typeofで判別できますが、通常は区別する必要がありませんif文の条件としても、boolean、objectのどちらでも同じに使えます。(すぐ後の追記を参照)条件式(b == true)なら大丈夫ですが、次のように書くとダメです。


if (b === true) {
// b === true のときの処理
}else {
// b === true でないときの処理
}

[追記]
nokturnalmortumさんよりのトラックバックにおいて、上の説明のなか:

if文の条件としても、boolean、objectのどちらでも同じに使えます。

という表現はいかがなものか、というご指摘がありました。おっしゃるとおり、「どちらでも同じ」は不適切でした。ごめんなさい。


var t = new Boolean(true);
var f = new Boolean(false);
として、次の8つの文を実行してみます。

/* 1-1 */ if (t) {alert("YES");} else {alert("NO");} // YES
/* 1-2 */ if (!f) {alert("YES");} else {alert("NO");} // NO

/* 2-1 */ if (t == true) {alert("YES");} else {alert("NO");} // YES
/* 2-2 */ if (f != true) {alert("YES");} else {alert("NO");} // YES
/* 2-3 */ if (f == false) {alert("YES");} else {alert("NO");} // YES

/* 3-1 */ if (t === true) {alert("YES");} else {alert("NO");} // NO
/* 3-2 */ if (f !== true) {alert("YES");} else {alert("NO");} // YES
/* 3-3 */ if (f === false) {alert("YES");} else {alert("NO");} // NO

ほんとのtrue, falseの場合はすべてYESのはずですが、結果は右端のコメントのようです。「同じように使える」は撤回します。やはり条件式は本物のbooleanがよろしいようで。懸念があるときは、(typeof b == 'boolean') のようなチェックを入れた方がよいでしょう。
[/追記]

説教や警告は心して聞くのが吉

JSLintが、「なるべく===を使え」という説教をしょっちゅうするので、僕は「===」を多用するのですが、今回はそれが変な挙動を引き起こしていました。とはいえ、その変な挙動がなければ、おおもとの間違いも見過ごしていたので、「===」の使用が悪いとは言えません。

JavaScriptの親切な仕様は、エラーを顕在化させないことにより、ときに酷く発見しにくいバグを生み出すこともあります。JSLintが吐き出すような、口うるさい警告にも耳を傾けた方がよいと思います。