variable-object

オブジェクト指向言語JavaScript 変数とオブジェクト

前回は、JavaScriptの文法規則 文、式、演算子についてまとめた。ここでは、オブジェクト指向を支援する機能を提供するオブジェクト指向言語であるJavaScriptにおける変数とオブジェクトについて解説する。

1 変数の宣言

変数の役割は値の格納やオブジェクトを名前で呼ぶことである。宣言しただけで何も代入していない変数の値はundefined値である。

 

2 変数と参照

変数が名前を付ける対象の代表はオブジェクトである。オブジェクトそれ自体には名前がなく、名なしのオブジェクトを名前で呼ぶために使うのが変数である。変数には基本型変数(値型変数)と参照型変数の区別がある。参照とは、オブジェクトの位置情報を指し示すモノと言える。

 

関数の引数(値渡し)

値渡し(call-by-value)とは、JavaScriptの代入演算は右辺から左辺への値のコピーと考えるといった規則のことである。

 

文字列と参照

文字列型は内部実装は参照型でも、言語仕様上は値型と考えられる存在である。理由は文字列型が不変型だからである。

 

オブジェクトと参照にまつわる用語の整理

変数aにオブジェクトの参照を代入したとき、このオブジェクトを「オブジェクトa」と呼ぶ。厳密的には「変数aから参照されるオブジェクト」である。

 

3 変数とプロパティ

変数とオブジェクトのプロパティは同じものである。変数はスコープの違いでグローバル変数ローカル変数(パラメータ変数を含む)の2種類に分けられる。グローバル変数はトップレベルコードで宣言する変数である。トップレベルコードとは関係の外に書いたコードである。ローカル変数は関数内で宣言する変数である。グローバル変数とローカル変数の両者とも実体はプロパティである。

 

4 変数名の解決

変数名の解決とは、コード上に(右辺値として)変数名を書いたときにその値を取り出すこと、あるいは代入の左辺に書いたときに代入先の場所を探すことである。トップレベルコードの変数名の解決はグローバルオブジェクトのプロパティを探すことになる。関数内の変数名の解決には、Callオブジェクトのプロパティ、グローバルオブジェクトのプロパティの順で探す。

 

5 変数の存在チェック

変数の存在をチェックするためには「var a = a || 7;」といったコードを使う。例えば宣言していない変数から値を取り出そうとする際に起きるReferenceError例外を回避する方法である。

 

プロパティの存在チェック

存在しないプロパティを読むとundefined値が返るだけでエラーではないが、undefined値に対してプロパティアクセスをするとTypeError例外が発生する。

 

6 オブジェクトとは

抽象データ型とオブジェクト指向

JavaScriptのオブジェクトを形式的に定義するとプロパティの集合である。プロパティとは名前と値のペアである。プロパティ値にはどんな型の値でも指定できる。

よく知られるオブジェクトの定義はデータと操作(手続き)を一体化したものである。この定義はオブジェクト指向を抽象データ型の文脈でとらえる考えである。いわゆるカプセル化、継承、ポリモーフィズム(多態)という3要素である。オブジェクトの振る舞いに焦点を当て、振る舞いの共通性を型として定義する。

 

インスタンスの協調とオブジェクト指向

オブジェクト指向プログラミングにはインスタンス間の協調に焦点を当てるものもある。いわゆるメッセージを送り合うオブジェクトという文脈で知られる。

 

JavaScriptのオブジェクト

JavaScriptが言語としてサポートするオブジェクト指向は後者の流儀に近い。すべてがオブジェクトであり、オブジェクト間の共通性は、同じオブジェクトから性質を引き継ぐ形で実現する。JavaScriptではこれをプロトタイプベースで実現している。

 

7 オブジェクトの生成

オブジェクトリテラル

JavaScriptプログラムの中でオブジェクトを使うには最初にオブジェクトを生成する必要がある。オブジェクトリテラルによるオブジェクト生成が1つの方法である。オブジェクトリテラルを使う具体的場面は以下の通り。

  • シングルトンパターン的な用途:クラスベースの世界であるクラスのインスタンスを1つに限定するための技法
  • 他値データの用途(関数の引数や返り値など)
  • オブジェクト生成を意図したコンストラクタ代わりの関数

 

コンストラクタとnew式

コンストラクタはオブジェクト生成のために使う関数である。形式的に説明すると以下の4つのことが言える。

  • コンストラクタ自体は普通の関数宣言と同じ形
  • コンストラクタはnew式で呼び出す
  • コンストラクタを呼び出すnew式の評価値は(新規に生成された)オブジェクト参照
  • new式で呼ばれたコンストラクタ内でthis参照は(新規に作成する)オブジェクトを参照する

 

コンストラクタとクラス定義

フィールドとメソッドを持つクラス定義をコンストラクタで実装した場合、以下の2つの問題がある。前者はプロトタイプ継承で、後者はクロージャで解決できる。

  • すべてのインスタンスが同じメソッド定義の実体のコピーを持つので効率(メモリ効率と実行効率)がよくない
  • プロパティ値のアクセス制御(privateやpublicなど)ができない

 

8 プロパティのアクセス

生成したオブジェクトはプロパティを通じてアクセスする。オブジェクト参照に対して、ドット演算子(.)もしくはブラケット演算子([])でプロパティにアクセスできる。

 

プロパティ値の更新

プロパティアクセス式を代入の左辺に書いて値を代入するとプロパティ値を書き換えられる。存在しないプロパティ名を指定するとプロパティの追加になる。delete演算式を使うとプロパティを削除できる。

 

ドット演算子とブラケット演算子の使い分け

ドット演算子は記述が簡潔であり、ブラケット演算子は汎用性に優れるという特徴を持つ。ブラケット演算子でしか書けないパターンは以下の3つに分類できる。

  • 識別子に使えないプロパティ名を使う場合
  • 変数の値をプロパティ名に使う場合
  • 式の評価結果をプロパティ名に使う場合

 

プロパティの列挙

プロパティ名の列挙はfor in文を使う。プロパティ値の列挙はfor in文の中でブラケット演算子を使うことで間接的に可能である。for each in文を使うと直接プロパティ値を列挙できる。

 

9 連想配列としてのオブジェクト

連想配列

配列とは数値をキーに値を引けるデータ構造のことである。ほとんどすべてのプログラミング言語にある基本的なデータ構造である。配列のキーは連続した数値なので順序のある値の集まりと見ることができる。連想配列とは、数値以外のキー(多くは文字列)や任意の型のキーを許してキーと値の集まりを扱うデータ構造のことである。連想配列をマップや辞書、ハッシュと呼ぶ言語もある。連想配列の主な用途はキーから値を取り出す操作である。

連想配列は要素の集合である。要素はキーと値のペアである。連想配列の基本操作は、キーで値の取得、要素のセット、要素の削除の3つである。キーから値を取得する方法はドット演算子もしくはブラケット演算子を使う。要素のセットはドット演算子もしくはブラケット演算子を左辺値にして代入式を書く。要素の削除にはdelete演算子を使う。

 

連想配列としてオブジェクトの注意点

連想配列としてのオブジェクトにはプロトタイプ継承に絡む注意点がある。プロトタイプ継承は他のオブジェクトのプロパティを継承して、あたかもオブジェクト自身のプロパティのように扱える仕組みである。

 

10 プロパティの属性

プロパティには属性(attribute)がある。ECMAScript第5版で定義された属性は以下の5つがある。

  • writable:プロパティ値の書き換え可能
  • enumerable:for in文で列挙可能
  • configurable:属性を変更可能。プロパティの削除可能
  • get:プロパティ値のゲッター関数を指定可能
  • set:プロパティ値のセッター関数を指定可能

 

11 ガベージコレクション

ガベージコレクションとは、使われなくなったオブジェクトのメモリは自動で回収される仕組みである。使われないオブジェクトとは、どのプロパティ(変数)からも参照されていないオブジェクトのことである。

 

12 不変オブジェクト

不変オブジェクト

不変オブジェクトとは生成後に状態が変更されないオブジェクトのこと。オブジェクトの状態は各プロパティの値で決まるので形式的にはプロパティの値を変更できないオブジェクトをさす。

 

不変オブジェクトの有用性

不変オブジェクトの活用は堅牢なプログラミングに有効である。その理由は、プログラムのバグの多くはオブジェクトの状態が不正に変化することで起きるからである。

 

不変オブジェクトの手法

JavaScriptオブジェクトを不変オブジェクトにするには以下の3つの手法がある。

  • プロパティ(状態)を隠し変更操作を提供しない
  • ECMAScript第5版の関数を活用する
  • Writable属性、Configurable属性、セッター、ゲッターを活用する

 

13 メソッド

JavaScriptの言語仕様にメソッドは存在しない。オブジェクトのプロパティに関数をセットしたものを便宜上メソッドと呼ぶだけである。現実的には次に述べるthis参照を使い、呼び出された関数の中でオブジェクトのプロパティにアクセスしたものをメソッドと呼ぶ。

 

14 this参照

this参照はJavaScriptのコードのどこでも使える読み込み専用の変数である。this参照はオブジェクトを参照する。

 

this参照の規則

  • トップレベルコードのthis参照はグローバルオブジェクトを参照
  • 関数内のthis参照は関数の呼び出し方法で異なる

 

this参照の注意点

JavaScriptのthis参照の参照先はメソッドの呼び方で変わる。例えば、レシーバオブジェクトがなかったり、別のレシーバオブジェクト経由で同じ関数を呼ぶと動作が変わる。

 

15 applyとcall

関数オブジェクトのapplyとcallというメソッドを使うと、呼び出した関数内のthis参照を指定した任意のオブジェクト参照にできる。つまりレシーバオブジェクトを明示的に指定できると見なせる。

 

16 プロトタイプ継承

プロトタイプ継承の形式的な理解は「クラス名.prototype.メソッド名 = function(メソッド引数) { メソッド本体 }」と覚えればよい。

 

プロトタイプチェーン

プロトタイプチェーンの前提は以下の2つである。

  • すべての関数(オブジェクト)はprototypeという名前のプロパティを持つ
  • すべてのオブジェクトは、オブジェクト生成に使ったコンストラクタ(関数オブジェクト)のprototypeオブジェクトへの(隠し)リンクを持つ

 

プロトタイプチェーンの具体例

オブジェクトobjのプロパティ読み込みをするとき、最初に自分自身のプロパティを探す。見つからない場合、次にオブジェクトMyClassのprototypeオブジェクトのプロパティを探す。この動作がプロトタイプチェーンの基本である。

 

プロトタイプ継承とクラス

次に、MyClass.prototypeオブジェクトを生成したコンストラクタのprototypeオブジェクトのプロパティを探す。

 

プロトタイプチェーンのよくある勘違いと_proto_プロパティ

  • 自分自身のプロパティの後、コンストラクタ自身のプロパティを探す
  • 自分自身のプロパティの後、オブジェクトのprototypeオブジェクトのプロパティを探す

 

プロトタイプオブジェクト

プロトタイプオブジェクトとは、オブジェクトの暗黙リンク(_proto_プロパティ)が参照するオブジェクトのことである。

 

プロトタイプオブジェクトとECMAScript第5版

プロトタイプチェーンの動作がわかりづらい要因の1つが暗黙リンクの存在である。この状況は、ECMAScript第5版でgetPrototypeOfメソッドが登録されて変わった。

 

17 オブジェクトと型

クラスベースの言語の場合、オブジェクトの型はひな形となるクラスや実装インタフェースで決まる。JavaScriptの場合、この観点でのオブジェクトの型はない。

 

型判定(constructorプロパティ)

オブジェクトからコンストラクタを知るために、オブジェクトのconstructorプロパティを使える。コンストラクタがわかると何をプロトタイプ継承したかわかる。

 

constructorプロパティの注意点

constructorプロパティはオブジェクトの直接のプロパティではなく、プロトタイプチェーンで探した先にあるプロパティである。

 

型判定(instanceof演算とisPrototypeOfメソッド)

instanceof演算でもオブジェクトの型を判定することができる。左辺にオブジェクト参照、右辺にコンストラクタを指定する。オブジェクトを右辺のコンストラクタで生成している場合、演算結果が真になる。

 

型判定(ダックタイピング)

ダックタイピングとは、オブジェクトの振る舞いを直接調べて型を判定する手法である。

 

プロパティの列挙(プロトタイプ継承を考慮)

直接のプロパティの存在だけを判定したい場合には、hasOwnPropertyメソッドを使う。

 

18 ECMAScript第5版のObjectクラス

ECMAScript第5版のObjectクラスのcreateメソッドは、オブジェクトリテラル、new式に続くオブジェクト生成の第3の公式手段である。第1引数にプロトタイプオブジェクト、第2引数にプロパティオブジェクトを渡す。プロトタイプオブジェクトにnullを渡すとObjectすらプロトタイプ継承しないオブジェクトを生成できる。

 

プロパティオブジェクト

createメソッドの第2引数には、キーがプロパティ名、値がプロパティディスクリプタの連想配列(プロパティオブジェクト)を渡す。

 

アクセッサ属性

get属性とset属性に関数を指定するとゲッターとセッターのアクセッサでのみ値にアクセス可能なプロパティを定義できる。アクセッサとvalue属性は排他の関係である。

 

19 標準オブジェクト

ECMAScript第5版が定義する標準組み込みオブジェクト(built-in objects)は以下の通り。

  • Object:すべてのオブジェクトの基底クラス
  • (通称)グローバルオブジェクト:このオブジェクトのプロパティがグローバル変数やグローバル関数
  • String:文字列クラス
  • Array:配列クラス
  • Function:関数クラス
  • Number:数値クラス
  • Boolean:ブーリアンクラス
  • Math:数学関数オブジェクト
  • Date:日付クラス
  • RegExp:正規表現クラス
  • JSON:JSONパーサオブジェクト
  • Error:エラー基底クラス
  • EvalError:評価エラークラス
  • RangeError:範囲違反を示すエラークラス
  • ReferenceError:不正な参照を示すエラークラス
  • SyntaxError:構文違反を示すエラークラス
  • TypeError:不正な型を示すエラークラス
  • URIError:不正なURIを示すエラークラス

 

20 Objectクラス

ObjectクラスはJavaScriptのすべてのクラスの基底クラスである。名称と役割はJavaのObjectクラスに類似しているが、継承の仕組みはJavaと異なりプロトタイプ継承である。

 

21 グローバルオブジェクト

グローバルオブジェクトはホストオブジェクトのルートに相当するオブジェクトである。トップレベルコードのthis参照でグローバルオブジェクトにアクセスできる。

 

グローバルオブジェクトとグローバル変数

グローバル変数とグローバル関数はグローバルオブジェクトのプロパティである。つまりObjectやStringという名前もすべてグローバルオブジェクトのプロパティ名である。

 

Mathオブジェクト

Mathオブジェクトは数学関数などを提供するオブジェクトである。

 

Errorオブジェクト

Errorクラスの関数またはコンストラクタ呼び出し、およびプロパティ。例えば「Error(message):Errorインスタンスを生成」など。

 

最後に

JavaScriptは簡潔なリテラル表記と動的型の性質からオブジェクトの扱いは容易である。ただし裏側の仕組みは現在主流の他のオブジェクト指向言語と異なるので注意が必要である。

次回は、関数自体の演算と状態を持つ関数 関数とクロージャについてまとめる。

パーフェクトJavaScript (PERFECT SERIES 4)


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>