はじめに
はじめまして。advent calendar初参戦の中村です!
ZealsにはRailsエンジニアとしてJoinしましたが、3ヶ月ほど前からReactをメインに書いています。
Reactの勉強と並行して一ヶ月ほどJavaScriptの勉強も行いました。
その際に『気をつけないとバグに繋がるな〜』と思った仕様について書いていこうと思います!!
+演算子
JavaScriptには数値の足し算と文字列連結の**『+』**があります。
以下のコードがどのような挙動になるか是非一度考えてみてください!
➀console.log(3 + 4 + '5');
➁console.log('3' + 4 + 5);
結果は
➀ '75'
➁ '345'
となります。
なぜか
JavaScriptの**『+』**はこのように処理されます。
- JavaScriptの処理は被演算子のデータ型から『+』が加算なのか、文字列連結なのかを判断する。
- 加算と文字列連結はいずれも左から右に評価される。
➀はまず(3 + 4)が加算として評価され、次に(7 + '8')が文字列連結として評価されます。
➁は('3' + 4)が文字列連結として評価され、次に('34' + '5')も文字列連結と評価されます。
ちょっと不思議な挙動ですよね。。。
+演算子は挙動がわかりづらいので、
`${4 + 5}`
こんな感じに書いたらわかりやすいかもしれません!
おまけ
面白かったので、色々試してみると
true + 1
> 2
false + false
> 0
1 + 'test'
>"1test"
'test' + false
> "testfalse"
3 + +'3'
> 6
といった感じになります。。。笑
NaNとisNaNとNumber.isNaN
NaNは、Not a Numberの頭文字をとった特殊な値です。
なるほど。。。どうやらNot a Numberから推測するに数値ではなさそうですね!
ただ、typeof演算子を使ってみると。。。
console.log(typeof NaN)
> 'number'
number!?!?!?
『Not a Number』とは一体。。。
その他にもNaNにはNaN自身を含め、どの値とも等しくならないという特徴があります!
NaN === NaN //false
そのため、NaNかどうかを確認するためには組み込み関数のisNaN()を使います!
以下のコードがどのような挙動になるか是非一度考えてみてください!
console.log(isNaN(1));
console.log(isNaN("1")
console.log(isNaN(Infinity));
console.log(isNaN(null));
console.log(isNaN("9"));
console.log(isNaN(true));
console.log(isNaN(false));
console.log(isNaN(NaN));
console.log(isNaN(undefined));
console.log(isNaN("1yen"));
結果は
console.log(isNaN(1)); //false
console.log(isNaN("1")); //false
console.log(isNaN(Infinity)); //false
console.log(isNaN(null)); //false
console.log(isNaN("")); //false
console.log(isNaN(true)); //false
console.log(isNaN(false)); //false
console.log(isNaN(NaN)); //true
console.log(isNaN(undefined)); //true
console.log(isNaN("1yen")); //true
となります。
NaN以外もtrueが返ってきているような気がします。。。
なぜか
isNaN関数の引数が数値型ではない場合、その値はまず数へと型を矯正されます。その後NaNかどうかがテストされます。
数値型に型を強制される際に結果がNaNではない数値となる非数値(とりわけ型強制されると0や1の値になる空文字列や真偽値プリミティブ)に対しては、falseが返されるといった事になります。
ちなみに
es6以降からNumber.isNaN関数が追加されました!
グローバルのisNaN()関数とは異なり、Number.isNaN()は強制的に引数が数値に変換されません。この関数は、数値型であり、かつ NaNである値が渡されたときのみ、trueを返すということです。
Number.isNaN(NaN); // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0/0) // true
//以下全てfalse
Number.isNaN(undefined);
Number.isNaN({});
Number.isNaN(true);
Number.isNaN(null);
Number.isNaN(1);
Number.isNaN('1');
Number.isNaN('1.1');
Number.isNaN('');
Number.isNaN(' ');
Number.isNaN('NaN');
Number.isNaN('test');
isNaNとNumber.isNaNの挙動の違いとかもしっかり押さえておきたいですね!
nullとundefined
JavaScriptにはnullとundefinedといった二つの特殊な型があります。
nullが取る値はnullのみで、undefinedも同様です。
「null」は空っぽで「undefined」は未定義であることを意味しています。
let x; //undefined
let y = null; //null
変数「x」は値が何も格納されてない、何も定義されてないのでundefinedになります。
変数「y」には値として「null」が定義されているので、この変数の中身は空っぽであることが明示されています。
「==(等価演算子)」と、「===(厳密等価演算子)」でnullとundefined確認してみましょう!!
console.log(null == undefined)
> true
console.log(null === undefined)
> false
このことから、値は同じですが、型が違うことが推測されます!!
そこでtypeof演算子でnullとundefinedを確認してみます!
console.log(typeof undefined)
> 'undefined'
想定通り'undefined'が返ってきました!
次にnullを確認して見ましょう!
console.log(typeof null)
> object
object!?!?!?
確かにnullとundefinedの型が違うことは確認できましたが、nullがobjectであるという、別の疑問が生まれました。。。
これは混乱しますね。。。
nullが何故このような仕様になっているかというと、『過去にこのように使われていたから』といった理由だけだそうです!
この仕様を変えるべきだという提案が度々されていますが、あまりにも多くの既存コードがこの動作に依存しているため、未だにこの言語仕様が残っているそうです。。。
これは仕方が無いと受け入れるしかなさそうですね。。。
その他
その他にもちょっと気になった事があったのでサクッと紹介します!!
日付処理の月が0から11
let date = new Date()
> 2019-12-01T18:17:08.862Z
date.getMonth(); // 11
何でや。。。
オブジェクトのlength
let obj = {a:"a",b:"b"};
obj.length
> undefined
rubyだと2を返してくれるのですが、JavaScriptだと一手間必要です!
Object.keys(obj).length
> 2
不親切な気がしないでも無いです。。。
空文字と空配列と空オブジェクトの真偽値
空文字("")と空配列([])と空オブジェクト({})の真偽値は以下のようになります
Boolean("")
> false
Boolean([])
> true
Boolean({})
> true
空文字のみfalseと判定されます。
終わりに
上記で述べてきたJavaScriptの仕様を、しっかりと抑えておきたいですね!(バグに繋がりそうなので。。。)
ここまで色々書いてきましたが、僕はJavaScriptが大好きなので、これからもJavaScriptを書き続けようと思っています!
それではここら辺で、明日の@suzuki-marさんに繋ごうと思います!
最後まで読んで頂きありがとうございました!