uhyohyo.net

JavaScript初級者から中級者になろう

二章第十四回 nullとundefined

このページの最終更新日:

今回はDOMから少し離れて、nullundefinedについて解説します。実はこれらはオブジェクトではなくプリミティブ値で、文字列、数値、真偽値に続く、第四・第五の値です。

「true」「false」と書くとその名の変数ではなくプリミティブである真偽値を表すのと同様に、「null」「undefined」と書くとそれぞれnull,undefinedという個別の値を表します。

null

意味的には、nullは「何もない」ということを意味します。「何もないということを教えるための値」ともいうことができます。

例えば、nullは次のような時に出現します。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p>test</p>

    <script type="text/javascript">
      var nodelist = document.getElementsByTagName('p');

      console.log(nodelist.item(100));
    </script>
  </body>
</html>

「null」と表示されます。

このサンプルでやっていることは、まずp要素のNodeListを変数nodelistに代入し、その「100番目」を取得しようとしています。

しかし、このサンプルにはp要素はひとつしかありません。当然、100番目なんてあるわけがありません。つまり、「存在しない」ということになります。

こういうときにnullが活躍します。このように存在しない場合、itemメソッドはnullを返します。これは、NodeListでもHTMLCollectionでも同じです。

他にもさまざまな場面で出てきます。例えば、getElementByIdで該当するidを持つ要素が無かったときもnullを返しますし、各種フォームコントロールが持つformプロパティで、そのコントロールがform要素に所属していなかった場合もnullです。

他に、次のような場合もあります。


var newelement = document.createElement('p');

console.log(newelement.parentNode);

これは、createElementで新しくp要素を作り、それのparentNode、つまり親要素を表示しています。

しかし、createElementで作った要素は、まだ木構造には加わっていないのでした。つまり、親が無いということになります。こういう場合にもnullが返るのです。

さらに、firstChildやlastChild(二章第四回)も、子ノードが1つもない場合は存在しないのでnullです。previousSiblingやnextSibling(二章第7回)も、存在しない場合があり、そういう場合nullです。

また、自分からnullを使う場合もあります。例えば、insertBefore(二章第六回)では、第二引数にノードの代わりにnullを渡すことができます。こうすると、そのノードは一番最後に追加されます。普通にappendChildでいいような気もしますが、この仕様は次のような場合に役に立ちます。


<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p>test</p>
    <script type="text/javascript">
      var newp = document.createElement('p');

      document.body.insertBefore(newp, document.body.firstChild);
    </script>
  </body>
</html>

p要素を作り、それをinsertBeforeでbody要素に追加しているだけです。

第二引数がdocument.bodyのfirstChild、つまり最初の子ノードです。それよりも前にだから、つまりは先頭に追加しようとしていることがわかります。

ここで、もしbodyに子ノードが無かったらどうでしょう。firstChildは上で解説した通りnullです。ここで、insertBeforeの第二引数が、「nullだったらどこに追加したら分からないから追加しない」のような仕様だった場合、結局追加されずに終わってしまいます。

ところが、この仕様があることで一番最後に追加されます。子ノードがないということは、一番最初に追加するのと一番最後に追加するのは同じで、目的が達成できたことになります。

undefined

undefinedは、直訳すると「定義されていない」という意味です。これは、定義する必要がない、つまり、普通触れられない、触れることを想定されていないというものです。

例えば、


var a = {};
console.log(a.aaa);  // undefined

という場合があります。aという空のオブジェクトをつくり、そのaaaというプロパティを表示しようとしています。aにはaaaというプロパティが存在しません。存在しないプロパティを取得しようとした場合、undefinedが返ります。

また、次のような場合もundefinedが出てきます。

function a(){
}
var b = a();
console.log(b);

何もしないaという関数をつくり、bにその戻り値を代入しています。しかし、関数aはreturnで何か値を返しているわけではありません。このように、何も返さない関数は、実はundefinedを返しています。return;のように、return文で返す値を省略した場合も同じです。

あと2例ほど紹介します。まず、定義だけされた変数です。以下のように、ローカル変数を定義するときに定義だけして、あとから代入することができるのを覚えているでしょうか。


function foo(){
  var x;

  // ...

  x = 3;  // xを3で初期化
}
foo();

ここで、ローカル変数として定義はされたが、まだ中身がない変数の値はどうなっているでしょうか。お察しの通り、undefinedです。


function foo(){
  var x;

  console.log(x);  // undefined

  // ...

  x = 3;  // xを3で初期化
}
foo();

最後に、関数の引数が足りない場合です。


function func(a, b){
  console.log(a);
  console.log(b);
}
func(5);

ここで定義された関数funcは、aとbという2つの引数を取ります。しかし、呼び出し時には引数を1つしか渡していません。

この場合、渡されたなかった引数にはundefinedが入っています。上の例ではaに5が入りbはundefinedとなるため、「5」「undefined」の順にログが表示されます。

if文

さて、少し話は変わります。今までif文には、真偽値を渡してきました。例えば、


if(a == 3){
 // 〜
}

という場合、a == 3はtrueかfalseを返します。それがifに渡され、trueなら真、falseなら偽というものでした。

では、ifの条件部分に真偽値以外の値を渡したらどうなるのでしょう。


if(10){
    console.log("真");
}else{
    console.log("偽");
}

この場合、「真」が表示されるので、10という数値は真であることが分かります。他にもいろいろ試してみましょう。

実は、ifに渡すと偽になる値は限られています。0NaN(これについて詳しくは第十一章第六回で説明します)、空文字列(つまり"")、falsenullundefined

これ以外の値は、全て真です。また、オブジェクトも全て真です。

(真と偽については七章第二回にもさらに進んだ説明があります。)

これを活用したのが、次の例です。


// 変数nodeは何かのノード
if(node.firstChild){
    処理
}

このコードでは、nodeが子ノードを持っているかどうかを判定しています。もちろん、以前紹介したhasChildNodes(二章第四回)を使えばこの判定はできます。しかし、こちらの方法でも問題なく判定できます。

なぜなら、ノードが1つでも子ノードを持っていれば、「最初の子ノード」が必ずあるため、node.firstChildにはノードのオブジェクトが代入されています。つまり、真となります。

対して、子ノードがひとつもない場合は、上で述べた通りnullです。nullは偽だから、子ノードが1つもない場合は偽ということになります。

すこし発展的な余談になりますが、nullとundefinedはどちらもプリミティブである点は同じですが、プログラム上に書いたときの扱いが少し異なります。

実は、nullはリテラルであるのに対しundefinedは「undefinedが入った変数」です。ただし、変数undefinedは書き換えできない変数なので、次のようにしてもundefinedを変えることはできません(無視されるか、strictモードではエラーとなります)。


    undefined = 5;  // すでにある変数undefinedに新しい値を代入
    console.log(undefined); // 5を代入したはずなのにundefinedが表示される(書き換え不可)
  

一方、nullはリテラルなので、変数名などにできません。つまり、次のコードは文法エラーです。


    null = 5;  // nullは変数ではないのでこれは文法エラー
    console.log(null);
  

undefinedは書き換えできませんが、新しいスコープでundefinedという変数を新しく作ることは、実はできてしまいます。


    var func = function(){
      var undefined = 5; // このスコープ内で新しい変数undefinedを定義
      console.log(undefined); // 5が表示される
    };
    func();
  

つまり、場合によっては、undefinedと書いてもundefinedを取得できない可能性があります。これの対策として、どんな場合でも確実にundefinedが取得できる方法を使う場合があります。

よく使うのは次のような書き方です。

console.log(void 0);

このように、void 0と書くとundefinedになります。これは、新しく登場した演算子「void」の効果によるものです。voidは与えられた値を無視して常にundefinedを返すような演算子です。

void 0の0は、voidによって無視されるために与えられた値で、0であることに特に意味はありません。例えばvoid "Hey!"とかvoid nullとかでも問題はありません。慣例的にここには0を置くことが多いようです。0は1文字なので最短で書けることも理由のひとつと思われます。

undefinedが欲しい場面でundefinedと書くかvoid 0は人によって別れるようです。これらは、変数undefinedに他の値を入れるというバカなことをしなければ同じことです。