JavaScript おれおれ Advent Calendar 2011 – 20日目

これだけ。

  • undefined
  • null
  • boolean
  • number
  • string
  • object

配列とか日付とかは、全部オブジェクトの一種です。

さらにこれらもobjectとそれ以外でも分かれていて、objectじゃない方をプリミティブ型と呼びます。

(※primitive … (形)原始的な、根本的な)

null以外の型を調べるにはtypeof演算子を利用します。

console.log(typeof undefined);  // => "undefined"
console.log(typeof null);  // => "object"  // "null"じゃない!
console.log(typeof true);  // => "boolean"
console.log(typeof 1);  // => "number"
console.log(typeof "abc");  // => "string"
console.log(typeof {});  // => "object"
console.log(typeof function() {});  // => "function"

typeof null"object"になるのはJavaScript初期のミスのようですが、ともかく仕様でそう決まっています。

関数に対してtypeofをすると"function"になりますが、これも一応オブジェクトの一種です。()を付けて実行できる、という点が特殊なので、特別扱いされているみたいです。今回はこれ以上詳しい事はやりません。

引数に気をつけろ

型がなんだ、という話もありますが、オブジェクト型とプリミティブ型の間には決定的な差があります。それは変数が参照になる値が入るか、という点です。

// 「1」を加える関数
function addOne(num, obj) {
  num += 1;
  obj.one = "wow!";
}

// 「1」を加えてみる
var n = 10;
var o = {};
console.log(n);  // => 10
console.log(o.one);  // => undefined
addOne(n, o);

console.log(n);  // => 10  // 増えてない
console.log(o.one);  // => "wow!"  // 追加されてる

オブジェクト型は変数や引数にはその実体ではなく、参照として格納されます。ですから複数の変数が同じオブジェクトを扱う状態になります。

一方プリミティブ型は実際にその値がコピーされますから、何か変更があっても、元の変数の内容は変化ありません。(正確に言うと、プリミティブ型の値自体は不変で、あくまで変数に格納されている値が変化した、という解釈になります。)

プリミティブ型を操作する関数は、戻り値でそれを返してやる必要があります。またオブジェクト型は実体が共有されてしまっているため、意図せず勝手に変更してしまう事があります。

気を付けましょう。

prototypeに気をつけろ

追記(2011/12/21):なんか勘違いしていたのでまるっと削除しときます。 (‘_`) ごめんね。

これ、自分でオブジェクトを作成する際にハマる事があります。

// 任意のデータ格納オブジェクト
function Storage() {}
Storage.prototype = {
  data: {},
  get: function(name) {
    return this.data[name];
  },
  set: function(name, value) {
    this.data[name] = value;
  }
};

上記のコード、何が問題かお分かり頂けるでしょうか。

// 色を格納
var colors = new Storage();
colors.set("りんご", "赤");
colors.set("うぐいす", "うぐいす色");

// 鳴き声を格納
var cries = new Storage();
cries.set("犬", "わんわん");
cries.set("うぐいす", "ほーほけきょ");

console.log(colors.get("りんご"));  // => "赤"
console.log(colors.get("うぐいす"));  // => "ほーほけきょ"  // あ、あれ?

Storage.prototype.dataは参照であるから、Storageインスタンスの .dataプロパティは、全て同じオブジェクトへの参照になってしまうわけです。ぎょぎょぎょー!

これを回避するには毎回別のオブジェクトを設定するようにすれば良いです。コンストラクターでやりましょう。

// 任意のデータ格納オブジェクト
function Storage() {
  this.data = {};
}
Storage.prototype = {
  data: null,  // コンストラクターで{}を設定
  get: function(name) {
    return this.data[name];
  },
  set: function(name, value) {
    this.data[name] = value;
  }
};

これで先ほどのコードでcolors.get("うぐいす")の戻り値が"うぐいす色"になります。

ああ、良かった。

(ぶつぶつ……)

というわけでtypeofは型の種類を調べるもので何のオブジェクトであるかを調べるものじゃないです。

typeof(null)objectだったりするのはどうかと思いますが、ArrayとObjectの区別がつかなかったりするのはいいんじゃないですかね。そういうのはinstanceofの領分だと思うのです、Dan Kogai先生。