JavaScriptのデータ型と変数の追記(jsのスコープ, nullとundefinedの違いについて)

JavaScriptのデータ型と変数 - Slow Danceの内容でjavascripterさんにツッコミをもらった部分を調べたのでまとめました。

  • ツッコミ内容

>の部分が僕が書いた部分です。その下の行からが指摘してもらった内容です

>ブロック内で宣言された変数は、そのひとつ外側のスコープを持つ。
ブロック文はスコープを作りません。なので最も内側のスコープ内を持ちます。
スコープを生成するのは、関数、with、letなどです。

>nullとundefinedの違い
undefinedはグローバル変数で書き換え可能なのに対し、nullは予約語です。
また、typeof(undefined)は”undefined”ですが、typeof(null)は”null”です。

>nullかどうか判定したかったら「undefined == target」
undefined==nullはtrueなので、nullかどうか判定するにはtarget===nullのようにする必要があります。

jsのブロックスコープ

基本的に、ブロック内で定義された変数は、そのブロックが関数内で定義されれば関数内がスコープ、関数外で定義されればグローバルスコープを持ちます。
ブロック文自体はスコープを生成しません。ブロック文は文をグループ化するだけ。

>> if(true){var x = 1}
null
>> x
1


これについて参考になる記事がありました。

重要:JavaScript にはブロックスコープがありません。ブロックを用いて導入された変数のスコープは、そのブロックがある関数やスクリプトになります。変数をセットする影響はそのブロックを越えて持続されます。つまり、ブロック文はスコープを持ち込まないということです。独立したブロックも構文的には正しいのですが、C や Java のブロックで果たされるような機能を期待しているのであれば、そのような機能は果たされないため、JavaScript で独立したブロックを使う必要はありません。


以下の記事によると、ブロックスコープがないというのは問題になる模様です。

function fun2(arg, flag) {
  x = arg;
  
  if (flag) {
   var x = arg + arg;
   return x;
  }
  
  return x;
}


この関数に対して以下のように実行してみます。

>> var x = 0
0
>> hoge(5, ture)
10
>> x
0

ガーン。hoge関数内の1行目のxはグローバル変数だから、hoge関数を実行した後のxは引数に渡した5で上書きされるはず。
しかし違うのです。元記事によると

そうです。関数定義においても、局所変数は実行前に準備されます。変数宣言(var文)がブロックのなかにあっても、そんなことには関係なくプログラム冒頭に宣言が集約されてしまうのです。ですから、fun2は、次と等価です。

function fun2(arg, flag) {
var x;
x = arg;
if (flag) {
x = arg + arg;
return x;
}
return x;
}

ブロックスコーピングに慣れていると、これは相当にショッキングな事実です。


たしかに、ブロックスコープがあれば、ブロック内でvarキーワードで宣言された変数はブロック内でのみ有効で、外の同じ変数名をもつ変数には影響を与えないですよね。


というか、宣言が集約してしまうということは、この場合もまずい。

function hoge(arg){
  alert(z);
  var z;
}

z = 1;
hoge(2)

//undefiedがalertの実行結果として表示される

まぁ、こんなことしないと思うけど。

ブロックスコープは、JavaScript1.7 では実装されているようです。但し、letというキーワードを使って変数を宣言しないと、今まで通り、その変数はブロックスコープを持ちません。

let によって宣言された変数は、その定義があったブロックと、その変数が再定義されていないすべてのサブブロックにスコープを持ちます。この場合、let は var に非常によく似た働きをします。おもな違いは var 変数のスコープがそれを囲む関数全体であることです:

function varTest() {
var x = 31;
if (true) {
var x = 71; // 同じ変数!
alert(x); // 71
}
alert(x); // 71
}

function letTest() {
let x = 31;
if (true) {
let x = 71; // 違う変数
alert(x); // 71
}
alert(x); // 31
}


なお、javascript1.7を使うには、以下の宣言が必要です。

<script type="application/javascript;version=1.7"/>

nullとundefinedの違い

まず、指摘いただいた通り、undefinedはグローバル変数、nullは予約語だったのですね

>> null = 1;
invalid assignment left-hand side
>> undefined = 1;
1
>> alert(undefined);
//1がalertの結果としてでる

ここのコメントによると

undefined が予約語じゃないのは、ECMA262 (JavaScript/1.3) で追加された仕様だからだと思います。
後から予約語を追加すると、既存のコードが動かなくなる可能性があるので、グローバルオブジェクトのプロパティとしたのでしょう。書換可能なのも、同様の理由からではないでしょうか

とのことです。

nullかどうかの判定

確かに、「undefined == target」だと、targetがnullの場合もtrueになってしまいます。

>> typeof(undefined)
undefined
>> var target = null;
null
>> undefined == target;
true

これには「===」を使う必要があるのですね。

>> undefined === target;
false
>> null === target;
true


===は「値とデータ型」が等しいときのみ真になる演算子です。nullとundeifnedは型が異なるので正しく判定できるというわけですね。

>> var a;
null
>> null == a;
true
>> undefined == a;
true
>> undefined === a;
true
>> null === a;
false
  • nullはnull型だった

typeof(null)の結果はobjectになります。

>> typeof(null);
object
>> typeof(new Array());
object
>> typeof(null) == typeof(new Array());
true

うひゃーこれは参った。しかし、厳密にははっきりと違う模様。何でnull型にならないんだろう。指摘して貰った時は、「nullだよ」って言われたのだが。jsのバージョンが違うのかな。1.5〜1.7は確認したけどobjectだった。

which returns "object" for the null type when in reality null is distinct from objects

まぁ、「null === target」 とやれば、「null == target」がtrueになり、かつ、「typeof(null) == typeof(target)」とならなきゃいけなくて、これを満たすのはnullだから問題ないのか。