アリソンパリッシュによる
このセクションでは、オブジェクトの新しい側面、つまりオブジェクトはメソッドを持つことができるという事実を見ていきます。メソッドは通常の関数とよく似ていますが、メソッドにはどの値に対して作用をするかという考えが組み込まれている点が、関数と異なります(メソッドが作用するのは、そのメソッドが属するオブジェクト)。メソッドの使用は、コードをよりクリーンにそして簡潔にします。
目次
なぜメソッドなのか?
メソッドを例で示す最も簡単な方法は、前のサンプルを改変することです。次のスケッチは前のサンプルで、画面のどこかをクリックすると、新しい”矩形”オブジェクトが配列に追加されます。draw()では、すべての矩形オブジェクトが画面に描画され、矩形が移動します。
このコードを汎用化する1つの方法は、display()関数とupdate()関数を作成することです。この2つの関数は両方とも、パラメータとしてオブジェクトを取り、そのオブジェクトのプロパティを操作します。
しかしこの方法には問題があります。それは(顕在化していない)潜在的な問題で、display()とupdate()関数が知っているのは、1種類のオブジェクトの処理方法だけだということです。したがって、別の種類のオブジェクト(円でも三角形でも顔でもツチブタでも何でも)を追加する場合には、その埋め合わせのためdisplay()とupdate()関数に手を入れる必要が出てきます。また操作しているオブジェクトがどんなものかを類推するにも、両方の関数で何らかのコードを書く必要もあるでしょう。これでは混乱してしまいます。
JavaScriptの作成者たちは、全能の英知をもって、この問題を回避する方法を組み込みました。すべての種類のオブジェクトの操作方法を知る関数を1つ作るのではなく、オブジェクトそれぞれに、操作に必要なすべての関数を持たせれば良いのではないか? それがメソッドでした。
(実際には、この考えはJavaScriptオリジナルのものではありません。多くのプログラミング言語は、メソッドのようなオブジェクト指向の機能をサポートします)
オブジェクトにメソッドを追加する
メソッドは、オブジェクトのあるキーに関する値である関数に過ぎません。次のコードを空のp5.jsスケッチで実行してみてください。
const rectangle = {
width: 100, // 幅
height: 200, // 高さ
area: function () { // 面積
return this.width * this.height;
},
perimeter: function () { // 外周
return 2 * this.width + 2 * this.height;
}
};
console.log(rectangle.area()); // 20000
console.log(rectangle.perimeter()); // 600
このコードは、rectangleという名前のオブジェクトを作成します。rectangleは4つの属性を持ちます。widthとheightの値は数値で、areaとperimeterは関数値です。この2つの関数はオブジェクトの中の値なので、メソッドと呼びます。
メソッドを使い始めるために知っておく必要のあるシンタックスが2つあります。1つめはメソッドを呼び出す方法です。まずオブジェクトに評価される式を書き、その後にドット(.)をつづけ、メソッドの名前を入力して、かっこを付けます(メソッドはまたパラメータを取ることもできます。その場合には、かっこの中は空にはなりません。詳細は後述します)。
2つめの新しいシンタックスはthisです。メソッド内部には、メソッドを持つオブジェクトを参照する、特別なキーワードthisがあります。thisを使うと、プロパティにアクセスしたり、プロパティを上書きしたり、新しいプロパティを追加したり、またオブジェクトのメソッドを呼び出すことすらできます。
次のスケッチは上記の修正版で、自身のdisplay()とupdate()メソッドを持つオブジェクトを、mousePressed()関数が配列に追加します。
オブジェクトの工場
これまで見てきたように、JavaScriptにはオブジェクトとメソッドを扱うときの慣習とシンタックスがあり、また”プロトタイプ”継承と呼ばれる概念もあります。これは、オブジェクトはほかのオブジェクトの”テンプレート”になることができ、最初のオブジェクトのプロパティとメソッド全部を引き継ぐ、というものです。継承についてはここで読むことができます。
本クラスでは継承については述べませんが、今後継承が使用できないようなコードを書く習慣におちいらないように、”継承が準備できた”状態のオブジェクトの作成方法を示したいと思います。この目的を追求すべく論じる2つのシンタックスは、newキーワードと.prototype属性です。
コンストラクタ関数とnewキーワード
すでに見たように、新しいオブジェクトを作成するJavaScriptコードの記述はいたって簡単です。たとえば、次の関数は新しいオブジェクトを作成して返します。
function createAsteroid(massVal, albedoVal) {
const asteroid = {
mass: massVal,
albedo: albedoVal,
population: 0
};
return asteroid;
}
var foo = createAsteroid(1000, 0.14);
console.log(foo.albedo); // 出力:0.14
通常JavaScriptでは、唯一の目的が新しいオブジェクトの作成である関数をコンストラクタ関数と呼び、newキーワードを使った、非常に特別な方法で呼び出します。
newで呼び出されたコンストラクタ関数は、その内部で使用できるキーワードthisを自動的に持ちます。またコンストラクタ関数では、明示的に何かを返す必要はなく、オブジェクトの希望する属性をthisに割り当てればよいだけです。
以下は上記サンプルを、newを使って書き直したものです。
function Asteroid(massVal, albedoVal) {
this.mass = massVal;
this.albedo = albedoVal;
this.population = 0;
}
var foo = new Asteroid(2000, 0.45);
console.log(foo.albedo); // 出力 0.45
コンストラクタ関数の名前は、慣習により、大文字で始めます。
.prototypeを使ってコンストラクタにメソッドを追加する
コンストラクタ関数を作成する目的は、(いつか、最終的に)継承を使用できるようにすることです。オブジェクトの工場を”継承が準備できた”状態にするためにさらに必要なのは、メソッドを、オブジェクト自体ではなく、コンストラクタ関数の.prototype属性に追加することです。たとえば、Asteroid関数が作成するオブジェクトにcolonize(移住)メソッドを追加するには、次のようにします。
function Asteroid(massVal, albedoVal) {
this.mass = massVal;
this.albedo = albedoVal;
this.population = 0;
}
Asteroid.prototype.colonize = function (settlerCount) {
this.population += settlerCount;
}
var foo = new Asteroid(2000, 0.45);
console.log(foo.population); // 出力:0
foo.colonize(123);
console.log(foo.population); // 出力: 123
関数値をAsteroid.prototype.colonizeに割り当てると、Asteroidコンストラクタから返されたオブジェクト上で呼び出すことのできる新しいメソッドが作成されます。これは、オブジェクト本体の内部で定義されたメソッドを呼び出すのとまったく同じです。
矩形に戻る
次のスケッチは、コンストラクタ関数とnewキーワード、.prototypeへのfunctionの代入を使って書き直した矩形サンプルです。
演習
上記スケッチを修正して、円や三角形などの複数のシェイプを描画できるようにします。シェイプのタイプごとにコンストラクタ関数とメソッドを作成します(新しいオブジェクトを加えるために、draw()内のコードを変更する必要はありますか?)