JavaScriptのオブジェクトとは
オブジェクトとは?
javascriptによるプログラミングでオブジェクトの意味を知る必要があるかどうかはよくわからないけど、これがわからないとなんか頭の片隅にひっかかって先に進みにくくなるのも事実。
オブジェクトは「もの」として説明される。例えば哺乳類や魚類は全体として大きなオブジェクトであり、イルカやクジラが魚類か哺乳類かどうかなんて人が勝手にイルカを哺乳類に分類しただけでイルカがどっちに分類されようとイルカはイルカである。
イルカが哺乳類に分類されるのは、
- 卵を産まない
- 肺呼吸
であることが理由であるが、魚類と共通して「泳ぐ」という動作を有している。人間だって「泳ぐ」というスキルは持っている。
卵を産まないとか肺呼吸等のオブジェクトの特徴のことをプロパティと呼び、泳ぐ等のオブジェクトの動作のことをメソッドと呼ぶ。つまり、オブジェクト(哺乳類)は、哺乳類特有の特徴(プロパティ)と哺乳類特有の動作(メソッド)を持っていると言うことができる。
上位のオブジェクトというのは所詮は空の箱に過ぎず、そこに「二本足で歩く」「頭が良い」「道具を使う」というプロパティをいれれば、サルと人間だけが該当するオブジェクトが完成し、追加で「言葉を話す」プロパティを加えれば、人間オブジェクトが完成する。
「人間.言語」のようにオブジェクトの後にドットをつけてそのあとにプロパティやらメソッドやらを記述することで、オブジェクトにあらかじめ格納されている値を取り出すことができます。(例:人間.言語→true、魚.言語→false)
強引ですが、基本的な考え方はこんな感じです。
用意されているオブジェクト
javascriptにはすでに用意されているいくつかのオブジェクトがあります。
例えばその中のString(文字列)オブジェクトにはlengthというプロパティがあって、String.lengthで文字列の長さを取り出すことができます。
var a = "りんご"; alert(a.length); //3と表示される
このようにすでに用意されているオブジェクトには、用意されているそのオブジェクトに使えるプロパティやメソッドが存在していてリファレンスを参照すれば誰もが自由に利用することができます。
関数オブジェクトの定義
オブジェクトを自分で作るために関数の定義について知らなければならないので間にこれを挟めます。
関数の定義方法は3つありますが、どれを使っても結果は同じですので好きな定義方法を使ってください。ただし特別な理由がなければ通常関数宣言を用います。
- 関数宣言文
- 関数リテラル文
- Functionコンストラクタによる定義
関数宣言文
1の関数宣言文は、
function fun(a,b){ return a + b; }
のような定義文のことで、fun();のような形で呼び出すことが可能。これはコンパイル時に読みこまれるので、どの場所に記述してもかまわない。
関数リテラル文
2の関数リテラル文は、
var fun = function(a,b){ return a + b; }
のように関数を変数funに代入するような形をとる。またこの場合functionのあとに関数名がない(匿名関数)であることが最大の特徴。
関数宣言に同じくfun();のような形で呼び出すことが可能。これはコンパイル時ではなく、その読み込みのタイミングで実行されるので、fun();の記述はfunctionの記述よりも後でなければならない。
Functionコンストラクタによる定義
3のFunctionコンストラクタによる定義は、
var fun = new Function("a", "b", "return a + b");
のようにnewをつけて{}の記述なし、つまりFunctionオブジェクトは関数そのもので、その引数がaやらbやらreturnになっています。
コンストラクタというのはオブジェクトを生成するために使用する関数(上でいえば、Function(引数))のことです。クラスと同じ名前になります(ただしjavascriptにはクラスはありません。理由は後述)。
すなわち関数は、「メソッド、コンストラクタ、関数呼び出し(それ以外)」という大きく3つの役割を担っているといえます。
newはオブジェクトを生成する演算子です。定義済みのFunctionオブジェクトに引数を設定して関数オブジェクトを生成している。もちろん引数はなくても関数オブジェクトは生成します。
例えばNumberオブジェクトを生成するときは、var a = new Number(3);と記述すれば、3という数値型のオブジェクトが作られます。しかし、var a = 3;でも同じ数値型のオブジェクトが作れてしまいますのでどちらを使うべきかを悩むと思います。newとコンストラクタによるオブジェクトの生成とリテラル式によるオブジェクトの生成どちらを選ぶのが良いか。
答えは、後者のリテラル式によるオブジェクトの生成が望ましいとされています。
例えばArrayオブジェクト生成の際に、var a = new Array(3);のように引数に1つの数値を入れると、配列の長さを指定したことになります。(→["","",""]が生成)。しかし、引数に一つの文字列を入れればそれは配列の要素とみなされます。(Array("3")→["3"])。このような困惑を防ぐためにもリテラル式によるオブジェクト生成が望ましいです。
ただし、100個の空配列を作るときとかには、いちいち["","",・・・・]と書くのは手間なので、new Array(100);として生成したほうがいいなど使い分けは必要です。
オブジェクトを自分で作る
用意されているオブジェクトを使わずor用意されているオブジェクトを組み合わせて、自分だけのオブジェクトを作成することもできます。
javascriptは関数や変数すらがオブジェクトでなんでもオブジェクトであるがゆえにオブジェクトを作成する方法は実に様々です。その中でもfunctionを使ったナチュラルオブジェクト(コンストラクタ)の作成とnew演算子を使ったクローンオブジェクト(インスタンス)の作成に大きく二分されます。
ナチュラルオブジェクトの作成
システムが用意していないオブジェクトを1から作る方法です。
オブジェクト生成にはnewを使いますね。
var d = new Object(); //object型の場合{}でもよい。 d.x = "aiueo"; d.y = function(n){ document.write(n+"だよ"); } d.y("太郎");//太郎だよ
ある任意のオブジェクト(ここではd)を作って、そのオブジェクトにプロパティやメソッドを追加しています。
下では、関数オブジェクトとしてhumanオブジェクトを作って、その中にプロパティpartsとcolorを定義して、引数に指定した文字列を代入、returnで関数を自体を返り値として返します。この場合のthisは関数オブジェクト自体を指しています。
function human(a,b){ this.parts = a; this.col = b; return this; } a = human("eye","red").col; alert(a); //red
関数内にreturnで返り値を指定してあげないと、その関数が実行された時の返り値はundefiedになってしまいます。ただし、CSSを操作して色を変えたりするような外部のデータをいじるもの(返り値を必要としない操作)であればreturnはなくても構いません。
プロパティは、「this.○○=変数」の形で指定します。一方メソッドはというと、「this.○○=関数」の形で指定します。すなわちプロパティは変数であり、メソッドは関数ということです。
コンストラクタ(この場合sikaku(a,b))という関数オブジェクトの中に、メソッド(これも関数オブジェクト)が入っているという形です。
//オブジェクトの作成 function sikaku(a,b){ this.tate = a; this.yoko = b; this.keisan = function(){ var c = this.tate * this.yoko; return c; } return this; } var a = sikaku(2,3).keisan(); var b = sikaku(2,3).tate; alert(a); //6 alert(b); //2 //関数の作成 function sikaku(a,b){ var tate = a; var yoko = b; var menseki = tate * yoko; return menseki; } var a = sikaku(2,3); var b = yoko; alert(a); //6 alert(typeof(yoko)); //undefined
比較のためオブジェクトの作成と関数オブジェクトの作成を載せています。上はプロパティとメソッドを定義していますが、下はプロパティもメソッドも定義しておりません。
両者の大きな違いは、関数ですと中身を分けれませんから返り値も選べませんが、オブジェクトならドットの後につながるメソッドやプロパティで複数の値を取り出すことが可能です。
関数はまるごとオブジェクトの中に格納できるので、下の関数オブジェクトをまるまる上のオブジェクトのメソッドに追加することもできます。自分専用のオブジェクトを作成して、その中にそのメソッドの形で関数を格納しておくと関数単体で覚えておくよりは整理しやすいのかもしれませんね。
//四角形関連のオブジェクト function sikaku(a,b){ this.tate = a; this.yoko = b; this.keisan = function(){ var c = this.tate * this.yoko; return c; } return this; } var a = sikaku(2,3).keisan(); alert(a); //6 //三角形関連のオブジェクト function sankaku(a,b){ this.takasa = a; this.yoko = b; this.keisan = function(){ var c = this.takasa * this.yoko / 2; return c; } return this; } var a = sankaku(2,3).keisan(); alert(a); //3
このように、別のオブジェクトであれば同じメソッド名(関数名)でも構いません。
クローンオブジェクトの作成
クローンオブジェクトを作るにはnew演算子を使います。これは、上で作ったコンストラクタを鋳型としてインスタンスを作る操作になります。
ArrayやStringといったシステムにもともと存在しているオブジェクトのメソッドやプロパティをそのまま継承する形でオブジェクトを作ることができます。とはいえ、普段何気なくやっていることなので特別難しく感じる必要はないかと思います。
先ほど作った四角形オブジェクトのクローンオブジェクトを作るときは、var clfun = new sikaku(3,4);とすれば、これが元のオブジェクトであるsikaku(a,b)のクローンオブジェクトとなります。
別の言い方をすると、元のオブジェクトのことをクラス=コンストラクタ、クローンオブジェクトのことをインスタンスということもできます。
また、前でも述べた通り、newを使わなくても以下のような形でオブジェクトは作ることができるし、むしろ、newを使わないほうのリテラル式によるオブジェクト生成のほうが推奨されます。
- var obj = new Array(); = var obj = [];と同じ
- var obj = new String(“りんご”) = var obj = “りんご”;と同じ
- var obj = new Object() = var obj ={};と同じ
ここで、オブジェクトには、
- プリミティブ型・・・null、undefined、Boolean、Number、String
- リファレンス型・・・Object、Array、Function等
の2つの種類があることに注意しなければなりません。
//string var a = "2"; //リテラル式 var b = new String("2"); //コンストラクタ alert(typeof a);//string alert(typeof b);//object //number var a = 2; var b = new Number(2); alert(typeof a);//number alert(typeof b);//object //array var a = ["abc",2]; var b = new Array("abc",2); alert(typeof a);//object alert(typeof b);//object //object var a = {x:1,y:2}; var b = new Object(); alert(typeof a);//object alert(typeof b);//object //function var a = function(){} var b = new Function(); alert(typeof a);//function alert(typeof b);//function
aは「string」と評価され、bは「object」と評価されます。これはaに代入している「"abc"」という値はstringのプリミティブ型であり、bに代入している「new String("abc")」で生成される値はオブジェクト型であることを示しています。
オブジェクト型は値のほかに、さまざまなプロパティやメソッドを持つことができます。一方でプリミティブ型は値のみを持ち、プロパティやメソッドを持つことができません
var a = "2"; alert(a.length);//1
しかしプリミティブ型に対しプロパティの参照やメソッドの呼び出しがあった場合には、以下のように一時的・暗黙的にオブジェクト型に変換されているのです。
var a = "2"; alert((new String(a)).length);
String.prototype.name = "no name"; var a = new String("abc"); b.name = "name is a"; alert(a.name);//name is a String.prototype.name = "no name"; var a = "abc"; a.name = "name is a"; alert(a.name);//no name //上から2番めは、alertする時に新たにオブジェクトを生成するので値を保持できない。 String.prototype.name = "none"; var a = "123"; (new String(a)).name = "name is a"; alert((new String(a)).name);
参考ページ(プリミティブとオブジェクトの違い)
この辺のことはきちんと理解するには、javascript独自のなprototypeという考え方を理解しなければなりません。
prototype(プロトタイプ)
javascriptはオブジェクト指向の言語(オブジェクト自体がメソッドやプロパティを持つ)ですが、他の言語と少し違ったオブジェクト指向の言語です。
JAVA、C++、Rubyといった多くのオブジェクト指向言語は、クラスという鋳型があって、それをもとにインスタンスと呼ばれる実体を生成するクラスベースオブジェクト指向言語です。
一方、javascriptはクラスやインスタンスというものとは少し違った、全てのオブジェクトは別のオブジェクトを元(プロトタイプ)にして独自の特徴を付加することで生成されるプロトタイプベースオブジェクト指向言語と言われています。
すでに作られているオブジェクトに対してプロパティやメソッドを追加したい場合には、2通りの追加方法があります。1つはオブジェクトにそのまま追加する方法、もう一つはprototypeという文字を間に挟めて追加する方法です。
例えば、先ほどのsikaku()オブジェクトに新しく、changeというメソッドを追加したいとしたとき、
//sikakuオブジェクトへのメソッド追加 sikaku.change = function(){ this.tate += 10; this.yoko += 20; } //sikaku.prototypeオブジェクトへのメソッド追加 sikaku.prototype.change = function(){ this.tate += 10; this.yoko += 20; }
のように記述することができます。二つの違いは、追加したメソッドがその関数をコンストラクタとして用いて生成したオブジェクトに継承されるかどうかです。
function sikaku(){}; //関数宣言による関数生成 //sikakuオブジェクトへのプロパティ追加 sikaku.tate = 10; alert(sikaku.tate); //10 //sikakuコンストラクタを使ったクローン生成 var sankaku = new sikaku(); alert(sankaku.tate) //undefined(継承されていない) //sikaku.prototypeオブジェクトへのプロパティ追加 sikaku.prototype.tate = 100; alert(sankaku.prototype.tate); //100 alert(sankaku.tate); //プロパティとして登録してなくても、100が表示される
わかりづらいが、prototypeオブジェクトに追加したプロパティやメソッドはそのクローン全てから参照されるが、個別の関数オブジェクトに追加したものはその関数オブジェクトでしか使うことができないということです。
このような暗黙の参照は、読み取り評価の時だけで、代入やdelete演算子を使った削除(delete sikaku.prototype.change)のような記述は参照をたどらないことに注意します。sikaku.tateの10とsikaku.prototype.tateの100は別々に存在しているため、削除は代入は個別に行うことができます。
この時objA.prop1を呼び出すと、objA自身にはprop1が無いため、暗黙の参照をたどり、prototypeにてprop1を見つけ30と評価されます
この場合、objA.prop1を呼び出すと、objA自身にはprop1があるため、その値である100と評価され、objB.prop1を呼び出すと、暗黙の参照をたどり30と評価される。
//四角形関連のオブジェクト function sikaku(a,b){ this.tate = a; this.yoko = b; this.keisan = function(){ var c = this.tate * this.yoko; return c; } return this; } sikaku.prototype.change = function(a,b){ this.tate += a; this.yoko += b; } var a = new sikaku(2,3); a.change(8,7); //tateが+8、横が+7される alert(a.keisan()); //100
NumberやStringのような組み込みオブジェクトに新たにプロパティやメソッドを追加する場合は、同様に、Number.prototype.change=function(){};のように記述しなければならない。というのは、これらのオブジェクトのプロパティはprototypeとlengthの2種類しかなく、他のプロパティやメソッドはすべてNumber.prototypeオブジェクト等prototypeオブジェクトのものであるためです。
しかしながら、組み込みオブジェクト(特にObjectオブジェクト)に追加すると、他の子オブジェクトすべてに適用されてしまう可能性があるため普通は避けます。
プロトタイプチェーン
プロトタイプの連鎖のことを指す。
sikakuオブジェクトにAというプロパティをprototypeで追加して、sikakuオブジェクトをコンストラクタとしてsankakuオブジェクトを生成、ついでsankakuオブジェクトをコンストラクタとしてmaruオブジェクトを生成したとすると、maruオブジェクトがsankakuオブジェクトのプロパティを継承(暗黙に参照する)していることを示します。
こうすると、任意のオブジェクトのプロパティを調べる際に、そのオブジェクト自身がプロパティを持っているのか、そのオブジェクトのプロトタイプが持っているのかがわからなくなります。これを調べるために、すべてのオブジェクトのprototypeオブジェクトが持っているhasOwnPropertyメソッドというのを使ってそのオブジェクト自身のプロパティを調べることができます。
オブジェクトクラスのオブジェクトとは
typeof関数でObjectが返るようなObjectクラスは、ほかの言語でいう連想配列やハッシュと同じふるまいをする。値の取り出し方法には下記のように2通りある。
var obj = {x:1,y:2}; var a =obj.x; //これはvar a = obj['x']としたのと同じ alert(a); //1
プロパティ値として[]内にキーを入れる場合はクオートでくくるのを忘れずに。
コメントor補足情報orご指摘あればをお願いします。
- << 前のページ
- 次のページ >>