ページ

2012年6月25日

型変換の挙動とイディオム: Javascriptを本格的に勉強し始めて驚かされたこと その1

nyuhuhuu cc
Javascript (JS) の勉強会を初めて2週目(第2回)にして言語仕様に驚かされることがたくさんあったため、まとめておこうと思います。ちなみに使っているテキストはこれです。


パーフェクトJavaScript (PERFECT SERIES 4)

プログラミング経験がない人は絶対に読むべきではないですが、他の言語でプログラミングの経験がある人がJavascriptを新たに勉強するには非常にオススメできるかなり良いテキストです。そう感じました。

同じ専攻の同期4人で勉強を進めていて、とりあえず初めから読んでいこうということになり、先週は1-2章を読み、今週は3章「Javascriptの型」を読み進めました。

ちなみに実行環境はFirefoxのWebコンソールやChromeのJSコンソールを使って対話的に行っています。
FirefoxはSpiderMonkey, Chromeはv8と処理系が異なるため、微妙に挙動が違うこともありますが、本ではSpiderMonkey準拠なので、Firefoxで試す方が良さそうです。

FirefoxのWebコンソール
FirefoxのWebコンソール
では本題。

1. 文字列型と文字列クラスの比較

Javascriptには同値比較演算子が2つあり、==と===で挙動が異なる。

var s1 = "abc"; // これは文字列値
var s2 = new String("abc"); // これは文字列クラスのインスタンス
s1 == s2; // => true
s1 === s2; // => false
これは、==が暗黙の型変換をしてから比較するのに対し、===が型変換をせずに両辺の比較を行うところに原因がある。===をstrict equalという。

2. 文字列値の暗黙の型変換

var s = "abc";
s.length; // => 3
"abc".length; // => 3

文字列値sは値、文字列リテラル"abc"はリテラルでありオブジェクトではないのだが、暗黙の型変換により形式上プロパティアクセスをしているように見える。
これと同様の現象は数値クラスでも起こり、文字列クラスや数値クラスをnewする必要性はなくなる。さらばクラスベースオブジェクト指向!

3. オブジェクト型のインスタンスは参照型

こういう現象も起こる。
var s1 = new String("abc");
var s2 = new String("abc");
s1 == s2; // => false
s1 === s2; // => false
s1+'' == s2+''; // => true
s1+'' === s2+''; // => true
文字列の内容は同じでも参照先のオブジェクトが異なるためfalseになる。一方、+''してやると暗黙の型変換により文字列値どうしの比較となるためtrueになる。

4. 文字列値から数値の型変換

数値クラスの関数呼び出しにより数値を得ることができる。
var n1 = Number(1); // => 1
typeof n1; // => "number"
var n2 = Number("1"); // => 1
typeof n2; // => "number"
n1 + n2; // => 2

Number("1x"); // => NaN
parseInt("1x"); // => 1
parseInt("x"); // => NaN
parseInt("0xff"); // => 255
parseInt("ff", 16) // => 255
parseInt("ff", 10) // => NaN
parseInt("0xff", 10); // => 0
parseInt("0.1"); // => 0
parseFloat("0.1"); // => 0.1

5. NaNの挙動

コードは書かないけど、NaNが現れたらどんな演算をしてもNaNになり、同値判定、比較演算も常にfalseが返される。値がNaNであることの判定をするにはグローバル関数のisNaNを使う。
こういった挙動の理由はNaNがNot a Numberの略語であり、「数値ではない」と考えれば何かと足し合わせたり、比較しようとしても結果を定義できないことを考えると理解しやすい。

6. Booleanへの型変換の挙動

これはよく考えると当たり前のものもあるが、驚かされる。Booleanへの型変換はBoolean関数呼び出しで明示的に行え、暗黙的にはifやwhile文の条件式の中で暗黙の型変換が行われる。
// Boolean値はそのまま
var t1 = Boolean(true); // => true
var f1 = Boolean(false); // => false

// 数値は0とNaNがfalseになり、それ以外はtrue
var t2 = Boolean(1); // => true
var t3 = Boolean(-1); // => true
var f2 = Boolean(0); // => false
var f3 = Boolean(NaN); // => false

// 文字列値は空文字列値""以外はtrueになる
var t4 = Boolean("abc"); // => true
var t5 = Boolean("false"); // => true
var f4 = Boolean(""); // => false

// null値、undefined値はfalseになる
var f5 = Boolean(null); // => false
var f6 = Boolean(undefined); // => false
中でもBoolean("false")がtrueになるのは注意したい。

7. nullとundefinedの違い

nullは何も参照していない状態を表すで、null型にはnull値の1つしかない。null値はリテラル値。
undefinedは何も定義されていない状態を表す変数で、undefined型にはnull同様undefined値の1つしかない。だがこれはリテラル値ではなくあくまで変数(読み込み専用)。

8. Javascriptのイディオム

3で書いたような表現はクライアントへの転送量を削減するためのイディオムだったりする。他のスクリプト言語でも普通に使われるようなものもあったが、面白いものがいくつかあったのでまとめてみる。
// 数値から文字列値
var n = 1;
n+''; // 数値1から文字列値"1"への型変換
typeof(n+''); // => "string"

// 文字列値から数値
var s = "1";
+s; // 文字列値"1"から数値1への型変換
typeof(+s); // => "number"

// Boolean型へ
!!1; // => true
!!0; // => false
!!''; // => false
!!null; // => false

// オブジェクト型からBoolean型
// この場合は何を受け取っても必ずtrueとなるので注意
Boolean(new Boolean(false)); // => true
Boolean(new Number(0)); // => true
Boolean(new String('')); // => true
!!1と!!0はすごいと思った。二重否定かぁ。

まとめ

今までクライアントサイドもそれなりに書いてきましたが、ググってコピペしたり、jQueryをこねくり回したりと、Javascriptと真剣に向き合ってきませんでした。このままではいかんと取り組んでみた結果、Javascriptに限らないものもありますが、面白い言語だなぁと感じました。やってみてよかったです。
Boolean("false")やBoolean(new Boolean(false))がtrueになったりと直感に反する部分もありますが、でもよく見てみると論理的で、ほかの動的型付けをする言語とはまた違うなぁという印象です。これからどんどん学習を進めていきますが、また新たな驚きが待っていると思うとわくわくします。この感じはrubyを初めて触ったとき以来かもしれない。非常に楽しみです。
Related Posts Plugin for WordPress, Blogger...