JavaScriptにおけるオブジェクト

投稿者: | 2016-05-05

ゴールデンウィークに入りました。幸運なことに今年は有休を取得することができたので10連休です。家族サービスとJavaScriptの復習に力を入れていこうと思います。

さて、JavaScriptを復習するにあたり、参考書としてサイ本開眼!JavaScriptを使用しています。どちらも読破できていないので、私にとっての良書かどうかはまだ判断できないのですが、言語仕様について詳しく書かれており(Amazonでの評価も高いし)、理解に苦しむこともないので継続して使っていこうと思います。

今回はJavaScriptにおけるオブジェクトについて、自分がまとめておきたいと思ったことを記事にします。

オブジェクトの生成方法

JavaScriptにおいては、オブジェクトを生成する方法はいくつか存在します。

コンストラクタ関数

コンストラクタ関数new演算子によるオブジェクトの生成です。式はJavaやC#にかなり似通っています。

var obj = new Object();   // コンストラクタ関数Object()を使ってObject型のオブジェクトを生成
obj.str = 'hoge';         // 生成したオブジェクトに文字列プロパティを付与
console.log(obj.str);     // hogeと表示される

しかしながら、オブジェクト生成時の式が意味する内容はJavaやC#とは異なります。JavaやC#でおなじみのコンストラクタですが、JavaScriptでもオブジェクトを生成するための関数として利用します。ただ、その扱いというか位置付けはJavaなどとは異なるものだと感じます。Javaなどではクラスの一メンバとしてコンストラクタが定義されますが、JavaScriptにおけるコンストラクタ関数はクラスのメンバというよりも、ファクトリ関数のようなものに近いと思います。というのも、コンストラクタ関数自体はただの変哲のない関数オブジェクトであり、あくまでもオブジェクトを生成することを前提に処理が定義されているだけで、定義方法は通常の方法と変わりありません(関数の使用目的によって分類名を変えているだけとイメージしています)。

さて、そのコンストラクタ関数ですが、通常の関数呼び出しではオブジェクトを生成することはできません。new演算子を一緒に使用することによって、オブジェクトを生成する、という特別な役割を果たすことができるようになります。オブジェクトの生成が完了したら、そのオブジェクトを呼び出しコンテキスト(=thisキーワードが指すオブジェクト)として、new演算子の右に指定した関数の処理を実行します。それが終了すると生成したオブジェクトを戻り値として返します。

Object()は組み込みコンストラクタ関数ですが、ユーザ自らコンストラクタ関数を定義することも可能です。

// 通常の関数定義と違いがないことがわかる
// ここでのthisは、コンストラクタ関数によって生成されるオブジェクトを指す
// すなわちthis.~は生成されたオブジェクトの固有なプロパティを指すこととなる
var Student = function (age, gender, major) {
    this.age    = age;
    this.gender = gender;
    this.major  = major;
    this.getMajor = function () {
        return this.major;
    }
}

var jack = new Student(33, 'male', 'history');

console.log(jack.getMajor());
console.log(jack.constructor);  // constructorプロパティはコンストラクタ関数への参照を持つ
console.log(Student);           // コンストラクタ関数そのものが出力される

オブジェクトリテラル

JavaScriptでは、コンストラクタ関数を使わなくてもオブジェクトを生成することができます。オブジェクトリテラルという表記をすることにより、ネイティブオブジェクトを生成することが可能です。

var tony = {
    "age" : 25,
    "gender" : "male",
    "major" : "chemistry",
    "getMajor" : function () {
        return this.major;
    }
}

console.log(tony.getMajor());
console.log(tony.constructor);  // コンストラクタ関数Object()が表示される

オブジェクトリテラルはコンストラクタ関数に比べて直観的にオブジェクトが生成できるように見えます。なお、オブジェクトリテラルによるオブジェクト生成においても、内部的にインタプリタでコンストラクタ関数を呼び出してオブジェクトを生成していることが最後のコードからわかります。しかしユーザ定義コンストラクタ関数(≒ユーザ定義クラス)に紐づくオブジェクトを生成することは、オブジェクトリテラルではできません(たぶん…)。

Object.create()

ECMAScript 5にて定義されたメソッドです。第一引数にプロトタイプを指定すると、そのプロトタイプを継承したオブジェクトを生成します。

var eren = Object.create({
    "age": 25,
    "gender": "female",
    "major": "english",
    "getMajor": function () {
        return this.major;
    }
});

console.log(eren.getMajor());
console.log(eren.constructor);  // コンストラクタ関数Object()が表示される

また、第二引数にプロパティを指定することにより、生成したオブジェクトにプロパティを設定することが可能です。

var kate = Object.create(Student.prototype,
    {
        age:    {writable: true, configurable: true, enumerable: true, value: 12},
        gender: {writable: true, configurable: true, enumerable: true, value: "female"},
        major:  {writable: true, configurable: true, enumerable: true, value: "literature"},
        getMajor:  {writable: true, configurable: true, enumerable: true, value: function () {
            return this.major;
        }},
    });

console.log(kate.getMajor());
console.log(kate.constructor);  //Studentコンストラクタ関数が出力される。

上記のコードではStudentコンストラクタ関数のプロトタイプを指定してオブジェクトを生成していますが、内部ではどのように実行されているのでしょうか。コンストラクタ関数の引数として第二引数で指定したプロパティ群をしようしているのか、それともコンストラクタ関数の引数にはundefinedを渡してオブジェクトを生成した後にプロパティを設定しているのか。このあたりはプロトタイプ継承を理解しなくてはいけないような気がするので、その後確認してみます。

JavaScriptオブジェクトはミュータブル

JavaScriptでは動的にプロパティを設定したり削除したりすることが可能です。イメージ的には、クラス定義を行いオブジェクトを生成した後でも、クラスの定義内容を自由に変更できる、といったところでしょうか。JavaやC#ではあまり考えられませんね。また、クラス定義の内容のようなもの(JavaScriptでいうとプロトタイプ)のプロパティだけでなく、生成したオブジェクト(すなわちインスタンス)に対しても動的にプロパティを設定できます。

function Student (age, gender, major) {
    this.age = age;
    this.gender = gender;
    this.major = major;
    this.getMajor = function () {
        return this.major;
    }
}

var ken = new Student(21, "male", "computer science");  //オブジェクトの生成
var mika = new Student(20, "female", "art");

// プロトタイプにプロパティを追加する
Student.prototype.getGender = function () {
    return this.gender;
}

// kenとmikaの両方にてメソッドを使用できる
console.log(ken.getGender());   // maleと出力される
console.log(mika.getGender());  // femaleと出力される

// インスタンスにプロパティを追加する
ken.getAge = function () {
    return this.age;
}

// インスタンスkenに対してプロパティを追加したので、mikaはメソッドを使用できない
console.log(ken.getAge());      // 21と出力される
console.log(mika.getAge());     // mika.getAge is not a function

まとめ

  • JavaScriptにおけるオブジェクトの生成方法について
  • ミュータブルオブジェクトとは

の2点について自分用まとめを記事にしました。