第2章 JavaScriptのオブジェクト

オブジェクト指向の概要

オブジェクトの概要と利用法

JavaScriptはオブジェクト指向プログラミングをサポートしています。オブジェクト指向プログラミングとは、プログラミングに現実世界のモノの概念を導入し、オブジェクト同士の連携によって命令を処理するプログラミングの技法です。オブジェクトはプロパティと呼ばれるデータと、メソッドと呼ばれる関数を複数持つことができる変数です。非常に便利なので、JavaScriptもあらゆるところでオブジェクトを利用しています。今までに登場したconsole.log()や配列、関数、正規表現などもオブジェクトで、JavaScriptが提供する機能のほとんどがオブジェクトと言えます。

便利な半面、オブジェクトと、オブジェクト指向プログラミングを正確に理解するのは難しいですが、深い理解がなくても初歩的な操作には困りません。ここではオブジェクト指向の難解な部分を省略し、オブジェクトの初歩的な操作方法、そして必要最低限の用語について重点的に紹介していきます。

オブジェクトの作成

習うより慣れよということで、早速ですがオブジェクトを作成し、参照する方法の実践に入ります。一番シンプルなオブジェクトの作成はオブジェクトリテラルです。オブジェクトリテラルはブレス( {} )を使ってオブジェクトを定義します。下記のコードで空のオブジェクトが作成され、変数objに代入されます。

var obj = {};

オブジェクトは、ブレス( {} )内に自分に属するプロパティ、メソッドを定義できます。オブジェクトリテラルでは名称と値はコロン( : )で区切り、複数のプロパティやメソッドがある場合はそれぞれをカンマで区切ります。

構文:ブレスを使ってオブジェクトを生成
// オブジェクトの作成
var オブジェクト名 = {
	// プロパティを定義
	プロパティ名 : 値, 

	// メソッドを定義
	メソッド名 : function ( 引数1 [, 引数2] [, …] ) {
		...
	}
};

オブジェクトのプロパティ

プロパティの定義

プロパティは配列のように複数の値を持つことができ、インデックスの代わりに名前で値を管理することができます。プロパティの名前と値はコロンで区切ります。次のコードはnameとageという2つのプロパティを持ったオブジェクトを定義しています。

// オブジェクトの作成
var obj = {
	// プロパティの定義
	name : "Hermann",
	age : 27,
};

nameプロパティは"Hermann"という文字列、ageプロパティは27という数値で初期化されています。プロパティ名の識別子として使えない文字、例えばハイフンや空白などが使われている場合はクォーテーションで囲む必要があります。次のコードはプロパティ名にハイフンが使われているので、クォーテーションで囲んであります。

var obj = {
	"first-name" : "Hermann",
};

プロパティの呼び出し

オブジェクトのプロパティを参照するには、ドット記法( . )を使う方法と、ブラケット( [] )を使った連想配列の記法があります。

構文:
// ドット記法
オブジェクト名.プロパティ名;

// 連想配列の記法
オブジェクト名["プロパティ名"];

ドット記法を使って、先程作成したオブジェクトのnameプロパティにアクセスします。

obj.name;
出力結果
Hermann

プロパティ名に識別として使えない文字がある場合、ドット記法は使えません。そのような際は、ブラケット( [] )を使った連想配列の記法を使ってください。

var obj = {
	"first-name" : "Hermann",
};

// ハイフンがあるのでこの表記は使えない
console.log( obj.first-name );
// ハイフンがあってもプロパティを参照できる
console.log( obj["first-name"] );
出力結果
NaN
Hermann

プロパティの追加

プロパティはオブジェクトの作成後に追加できます。次のコードはオブジェクトのプロパティとしてsexを追加しています。

obj.sex = "female";
console.log( obj.sex );
出力結果
female

プロパティの削除

プロパティの削除はdelete演算子を使います。

// sexプロパティの削除
delete obj.sex;

// sexプロパティを呼び出す
console.log( obj.sex );
出力結果
undefined

オブジェクトのメソッド

メソッドの定義

メソッドはオブジェクトに属する関数です。コード的にはプロパティの値として設定します。次のコードでは、コンソールに出力する簡単な無名関数を作成し、メソッドとしてオブジェクトに登録しています。

出力結果
var obj = {
	// メソッドを定義
	meth : function ( ) {
		console.log( "Hello" );
	}
};

メソッドの呼び出し

先ほど作成したobj変数を使ってメソッドを呼び出してみましょう。プロパティと同様にオブジェクト名とメソッド名をドット演算子で繋ぎ、メソッドの場合はさらにメソッド名の後に()を付加し、必要な際は引数を指定します。

構文:
// メソッドを呼び出す
オブジェクト名.メソッド名( 引数1[, 引数2][, …] );
// メソッドの実行
obj.meth();
出力結果
Hello

new演算子とコンストラクタ

ブレス( {} )を使ったオブジェクトリテラルは簡単にオブジェクトを作成できて便利ですが、オブジェクトが複数必要な際はオブジェクト作成毎に再定義する必要があり、コードが冗長となりやすいという問題があります。例えばプロパティやメソッドは同じで、プロパティの値だけ違うオブジェクトを複数作りたい場合、次のコードのようにオブジェクト毎にプロパティとメソッドを再定義する必要があります。

// オブジェクトの定義
var obj1 = {
	// プロパティ(それぞれ別の値)
	name : "Hermann",

	// メソッド(共通)
	greet : function(){
		console.log( "Hello, My name is " + this.name );
	}
};

// オブジェクトの定義
var obj2 = {
	// プロパティ(それぞれ別の値)
	name : "Ernest",

	// メソッド(共通)
	greet : function(){
		console.log( "Hello, My name is " + this.name );
	}
};

// メソッドの実行
obj1.greet();
obj2.greet();
出力結果
Hello, My name is Hermann
Hello, My name is Ernest

全く同じコードが複数回登場するとしたら、そのコードには改善の余地があるかもしれません。特に今回のコードはオブジェクトが増えるたびにほぼ同じオブジェクト定義のコードが繰り返されるため、改善が必要です。このように複数のオブジェクトが必要な際は、必要とされるオブジェクトの設計を行い、そのオブジェクトをコピーできるようにします。こうすることで、オブジェクトがいくつ必要であっても、オブジェクトの定義は一度だけとなります。

次のコードは先程のコードと同じ処理を関数とnew演算子を使って書き換えています。

// 関数の作成
function Person( name ) {  ・・・①
	// プロパティ(値に引数を代入)
	this.name = name;  ・・・②

	// メソッド
	this.greet = function(){
		console.log( "Hello, My name is " + this.name );
	}
}

// Personのオブジェクトを作成
var obj1	= new Person( "Hermann" );  ・・・③
var obj2	= new Person( "Ernest" );

// メソッドの実行
obj1.greet();
obj2.greet();
出力結果
Hello, My name is Hermann
Hello, My name is Ernest

先程のオブジェクトリテラルを使ったコードではオブジェクトが必要な数だけオブジェクトの定義を行っていましたが、今回のnew演算子を使う方法は、プロパティとメソッドを関数内で一度だけ定義しています。それぞれの記述方法については後で詳しく説明するとして、ここではオブジェクトリテラルとの相違点に注目していきます。

まずオブジェクトの定義については、オブジェクトリテラルではなく関数(①)を使っています。その関数内のプロパティの宣言部分(②)では、nameプロパティの値に引数を代入しています。これで、プロパティとメソッドの定義は1度だけで、なおかつ関数を呼び出す際(③)にプロパティの値を自由に設定することができます。

コードの行数に注目するとあまり大きな違いがありませんが、必要なオブジェクトの数がもっと多かったり、プロパティやメソッドの数が多い場合、オブジェクトリテラルの方はほぼ倍々でコードが増えていきます。それに対して、new演算子を使う方法の方は呼び出し部分(③)の行が増えていくだけで済みます。

コンストラクタの作成

先のコードでnew演算子から呼び出されたPerson()関数は、オブジェクト指向でいうコンストラクタに当たります。JavaScriptではコンストラクタを作成するための専用の構文はなく、new演算子から呼び出された関数をコンストラクタと呼びます。

コンストラクタはnew演算子から呼び出されると、その内部で定義したプロパティを初期化し、呼び出し元にオブジェクトの参照を返します。このときコンストラクタが返すオブジェクトをインスタンスと呼びます。先のコードではobj1とobj2がインスタンスに当たります。このインスタンスを使うことで、コンストラクタで定義したプロパティやメソッドにアクセスすることができます。

[画像]

コンストラクタの正体は関数なので、構文も同じです。ただ、コンストラクタの場合プロパティやメソッドを定義するためにthisキーワードを使うので、その点が関数と違います。また、構文の違いではありませんがJavaScriptでは関数とコンストラクタの区別がつきやすいように、コンストラクタの場合は名前の1文字目を大文字にするのが慣習になっています。

構文:
// コンストラクタの作成
function コンストラクタ名( 引数1 [, 引数2] [, …] ) {
	// プロパティを定義
	this.プロパティ名 = 値;

	// メソッドを定義
	this.プロパティ名 = function(){
		...
	}
}

コンストラクタの中でthisキーワードを使った場合、thisはコンストラクタ自身を意味し、thisキーワードで宣言したプロパティやメソッドはそのコンストラクタに属します。逆に言うと、thisキワードを使わずに宣言したプロパティやメソッドはコンストラクタに属さないので、インスタンスを通して呼び出すことができません。通常の関数内変数と同様に隠蔽されます。次のコードはthisキーワードを使わずname変数を定義しています。

// コンストラクタの定義
function Person( NAME ) {
	name = NAME;
}

// new 演算子を使ってインスタンスを生成
var person	= new Person( "Hermann" );

// nameはプロパティではないのでアクセスできない
console.log( person.name );
出力結果
undefined

Person()内で宣言したnameはオブジェクトのプロパティではないので、インスタンスからアクセスできませんでした。

new演算子

コンストラクタが理解できたところで、今度はそれを呼び出すnew演算子に取り掛かりましょう。new演算子はコンストラクタを呼び出し、オブジェクトのインスタンスを作成します。

構文:
インスタンス名 = new コンストラクタ名();

次のコードは、new演算子を使ってインスタンスを作成しています。コンストラクタは先程作成したPerson()を使っています。

// コンストラクタの定義
function Person( NAME ) {
	// プロパティ
	this.name = NAME;
}

// Person()のインスタンスを作成
var obj1	= new Person( "Hermann" );

// nameプロパティの値を出力
console.log( obj1.name );
出力結果
Hermann

ここまででプロパティ、メソッド、コンストラクタ、new演算子、インスタンス、thisキーワードを説明しました。オブジェクト指向の基礎部分はだいぶ説明できたと思います。オブジェクト指向にはこのほかにクラスという概念があるのですが、JavaScriptにクラスが追加されたのはECMASCript2015(ES6th Edition)からで、元々はクラスがありませんでした。

インスタンス

new演算子やオブジェクトリテラルで生成されたオブジェクトはインスタンスと呼ばれます。インスタンスはいくつでも作成可能です。次のコードのobjA、objBはPersonコンストラクタのインスタンスです。objA、objBは同じコンストラクタをもとに生成されますが、値はそれぞれ別に持つことができます。

// コンストラクタの定義
function Person( age ) {
	// プロパティ
	this.age = age;
}

// objA.ageの値を設定
var objA	= new Person();
objA.age	= 20;

// objB.ageの値を設定
var objB	= new Person();
objB.age	= 40;

// 出力
console.log( "objA.age: " + objA.age );
console.log( "objB.age: " + objB.age );
出力結果
objA.age: 20
objB.age: 40

thisキーワード

thisキーワードは呼び出される状況により何を参照するかが変わります。簡単な考え方としては、オブジェクトに内包されていればそのオブジェクトを参照、内包されていなければwindowオブジェクトを参照します。

// オブジェクトに内包されていないので、thisはwindowオブジェクトを参照します。
console.log( this.location.href );

// オブジェクトに内包されているので、thisはそのオブジェクトを参照します
function Person( age ){
	// thisはオブジェクト自身を示し、ageはプロパティとして定義される
	this.age = age;

	// thisはオブジェクト自身を示し、methはメソッドとして定義される
	this.meth = function(){
	console.log( this.age );
}
}

// インスタンスの生成
var obj = new Person( 10 );

// インスタンスのメソッドを実行
obj.meth();

※呼び出し側によるところがあるので、実際はこの通りではないケースもあります。

オブジェクト用のユーティリティ

for in文

for in文はオブジェクトの列挙可能な全てのプロパティにアクセスします。for in文は括弧の中に、inを挟んで右側に繰り返し処理の対象としたいオブジェクトと、左側にオブジェクトの要素を代入する変数を指定します。

構文:
for ( 変数 in オブジェクト) {
	命令文
}

for in文はプロパティの順序を保証しないのて、宣言したプロパティの順番通りにアクセスするとは限りません。また、メソッドなどもアクセスしてしまうので注意が必要です。

var obj = {
	// プロパティの宣言
	name : "Hermann",
	age : 27,
	// メソッドの宣言
	meth: function(){
	}
};

// for in文を使った繰り返し処理
for ( var name in obj ) {
	// プロパティを出力
	console.log( name + " : " + obj[name] );
}
出力結果
name : Hermann
age : 27
meth : function (){
}

オブジェクト指向の用語集

オブジェクト指向にはインスタンス、プロパティ、メソッドといった専門用語が多くあるので、これらの名前になれることが大事になります。この節で登場したオブジェクト指向の用語をリストしておくので、先の章に行って用語の意味がわからなくなった際はぜひこちらに戻って参照ください。

用語 説明
クラス オブジェクトの設計図のようなもので、プロパティやメソッドを定義したものです。
オブジェクト クラスやインスタンスなどの総称です。
インスタンス クラスをもとにnew演算子で生成したオブジェクトです。クラスが設計書としたら、インスタンスはその実体です。
プロパティ オブジェクトに属する値です。
メソッド オブジェクトに属する関数です。
コンストラクタ インスタンス化するときに最初に呼び出されるメソッドです。

関連記事