JavaScriptのオブジェクトリテラル・コンストラクタ関数・クラスについて

JavaScriptのオブジェクトについてまとめました。

オブジェクトとは

オブジェクトとは、プロパティとメソッドが集まったもの。プロパティはオブジェクトのデータや属性といったものにあたり、メソッドはオブジェクトが行う処理のこと。

オブジェクトの作成方法の種類

JavaScriptでオブジェクトを作成する方法はオブジェクトリテラル、コンストラクタ関数、クラスを使う方法の3種類ある。

オブジェクトリテラル

オブジェクトリテラルで最も手軽にオブジェクトを作成できる方法で、{}を使って書く。コンストラクタ関数やクラスと異なり、引数に値を渡すといったことはできないので固定の値を持ったオブジェクトになる。

const person = {
    // プロパティ
    age: 10,

    // メソッド
    hello: function() {
        console.log('hello')
    }
}

{}によって作られたオブジェクトはJavaScriptのビルトインオブジェクトであるObjectインスタンス

ビルトインオブジェクトとは、JavaScriptにもともと定義されているオブジェクト。

Objectは全てのオブジェクトの元となるオブジェクト。

Objectnew演算子を使ってインスタンスを生成することもできるが、{}の方が簡潔に書けるため使う必要はない。

const obj = new Object()
console.log(obj) // {}

プロパティへのアクセス方法

プロパティにアクセスするにはドット記法とブラケット記法の2種類ある。

// ドット記法
console.log(person.age)

// ブラケット記法
console.log(person['age'])

存在しないプロパティにアクセスするとundefinedが返される。

console.log(person.name)

コンストラクタ関数

コンストラクタ関数は複数の似たオブジェクトを作成するための関数で、関数名は大文字から始める。

コンストラクタ関数を定義しただけではオブジェクトは作成されず、new演算子を使って実際にオブジェクトを作成する。

仮引数を作っておき、new演算子インスタンス化するときに引数に値を渡すことで、構造は同じだが値が異なるオブジェクトを作成できる。

function Person(name) {
    // プロパティ
    this.name = name
}

// インスタンスを作成
const takashi = new Person('takashi')
const hanako = new Person('hanako')

console.log(takashi) // Person {name: 'takashi'}
console.log(hanako) // Person {name: 'hanako'}

インスタンスメソッド・プロトタイプメソッド

コンストラクタ関数に直接記述した関数はインスタンスメソッドになる。

function Obj() {
    // インスタンスメソッド
    this.fn = function() {
        console.log('fn')
    }
}

インスタンスメソッドはインスタンスごとに作られるメソッド。そのためインスタンスの数だけメソッドが定義される。

インスタンス固有の動作やインスタンスの状態と処理が結びつく処理を書きたいときはインスタンスメソッドを使う。


一方、インスタンス間で共通の処理を実装したいときはプロトタイプメソッドを定義する。 プロトタイプメソッドはコンストラクタ関数のprototypeオブジェクトに記述する。

インスタンスメソッドはインスタンス化したオブジェクトそれぞれで定義されるが、プロトタイプメソッドはprototypeオブジェクトに定義されたメソッドへの参照を保持するので、1つだけ定義するだけで済む。

1つだけで済むということはそれだけメモリの消費が少なくなる。

イメージとしてはプロトタイプは親のオブジェクトであり、親のオブジェクトにメソッドを定義することで、子にあたるインスタンスで共通して親のメソッドを使えるということ。

function Person(age, name) {
    this.age = age
    this.name = name
}

// プロトタイプメソッド
Person.prototype.fn = function() {
    console.log('fn')
}

コンストラクタ関数はただの関数

コンストラクタ関数はnew演算子を使えばオブジェクトを生成できるというだけで、実際はただの関数。逆に言えば普通の関数もnew演算子を使えばコンストラクタ関数としてオブジェクトを作成できる。

// 関数宣言
function fn() {}

// 関数をnew演算子でインスタンス化
const obj = new fn()
console.log(obj) // fn {}

// コンストラクタ関数
function Fn() {
    console.log(this)
}

// コンストラクタ関数を関数として実行
Fn() // Window

コンストラクタ関数はオブジェクトを作るためのものだが、関数としてつかうこともできてしまうため、追加されたオブジェクトを作るための構文であるクラスを使ったほうが良い。

余談

コンストラクタ関数と似た用語にFunctionコンストラクタがあり、Functionコンストラクタと似た用語にFunctionオブジェクトがある。

Functionオブジェクト

Functionオブジェクトは組み込みのオブジェクトで、関数そのもの。関数宣言や関数式などで作られる関数は全てオブジェクト。

Functionコンストラク

FunctionコンストラクタはFunctionオブジェクトを作るための組み込みオブジェクト。

const obj = new Function('console.log(this)')
obj()

ただし非推奨なので関数式などを使って関数を作るのが普通。

クラス

class構文はES6から追加されたもので、コンストラクタ関数を書きやすくしたもの。

コンストラクタ同様、1文字目を大文字にする。

他の言語のクラスとは異なり、内部的にはプロトタイプで動いている。

class Person {
    constructor(age, name) {
        this.age = age
        this.name = name
    }

    hello() {
        console.log('hello')
    }

    // このメソッドの書き方はNG
    // hello: function() {}
}

const takashi = new Person('10', 'takashi')

コンストラクタ関数同様、new演算子を使ってインスタンス化する。

constructorはクラスが必ず持つ、インスタンス化したときに実行されるメソッド。

インスタンスメソッド・プロトタイプメソッド

constructor内に定義したメソッドがインスタンスメソッドになり、class直下に定義したメソッドがプロトタイプメソッドになる。

同じ名前のインスタンスメソッドとプロトタイプメソッドを定義しても上書きされずエラーにならない。インスタンスメソッドはプロトタイプメソッドより優先して実行される。

class Obj {
    constructor() {
        // インスタンスメソッド
        this.fn = function() {
            console.log('instance')
        }
    }

    // プロトタイプメソッド
    fn() {
        console.log('prototype') 
    }
}

const obj = new Obj()

// インスタンスメソッドがプロトタイプメソッドより優先的に呼ばれる
obj.fn() // instance

継承

継承する時はextendsを使う。継承することで、親のプロパティやメソッドを子で使用することができる。

// 親のクラスを定義
class Animal {
    constructor(name) {
        this.name = name
    }

    speak() {
        console.log(`${this.name}の鳴き声`)
    }
}

// 子のクラスを定義
class Dog extends Animal {}

// インスタンス化
const sibaken = new Dog('柴犬')

// 親のメソッドを使える
sibaken.speak() // 柴犬の鳴き声

親のメソッドを上書きする

子クラスで親のメソッドを上書きしたい時は子クラスで同じ名前のメソッドを定義する。

class Animal {
    constructor(name) {
        this.name = name
    }

    speak() {
        console.log(`${this.name}の鳴き声`)
    }
}

// 子のクラスを定義
class Dog extends Animal {
    // 親のメソッドを上書き
    speak() {
        console.log(`${this.name}が吠えた`);
    }
}

// インスタンス化
const sibaken = new Dog('柴犬')

// 親のメソッドを使える
sibaken.speak() // 柴犬が吠えた

親クラスのconstructorを子クラスでも実行する

親クラスのconstructorを子クラスでも使いたい時はsuperという特殊な変数を使う。

子クラスのconstructor内でsuperを使うことで子クラスをインスタンス化した時に親クラスのconstructorを呼び出せる。

class Parent {
    constructor(name) {
        this.name = name
        console.log('親のconstructor');
    }
}

// 子のクラスを定義
class Child extends Parent {
    constructor(name) {
        super(name)
        console.log('子のconstructor')
        console.log(this.name) // 子供
    }
}

// インスタンス化
const child = new Child('子供')

子クラスのconstructorsuperを記述しないとUncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorが表示される。

また子クラスのconstructorsuperそれぞれの引数に親クラスのconstructorと同じ引数を指定しないまま子クラスでその引数を使おうとするとundefinedになる。

親クラスのメソッドを子クラスでも使う

superを使うことで親クラスのメソッドを子クラスでも使うことができる。

class Animal {
    constructor(name) {
        this.name = name
    }

    speak() {
        console.log(`${this.name}の鳴き声`)
    }
}

// 子のクラスを定義
class Dog extends Animal {
    // 親のメソッドを上書き
    speak() {
        // 親のメソッドをそのまま使う
        super.speak()
        console.log(`${this.name}が吠えた`);
    }
}

// インスタンス化
const sibaken = new Dog('柴犬')

// 親のメソッドを使える
sibaken.speak()

クラスは関数として呼び出せない

コンストラクタ関数と異なり、クラスは関数として呼び出すことはできない。

class Obj {}

Obj() // Uncaught TypeError: Class constructor Obj cannot be invoked without 'new'

まとめ

  • 1回限りのオブジェクトを作る時はオブジェクトリテラルを使う
  • 複数の似たオブジェクトを作成したいときはコンストラクタ関数やクラスを使う
  • 関数としても扱えてしまうコンストラクタ関数ではなく、オブジェクトを作るためのクラスを使うべき
  • クラスは内部的にはプロトタイプベースで動作する
  • インスタンスメソッドは個々のインスタンスごとに作られるメソッドで、プロトタイプメソッドはインスタンス間で共有するメソッド