関数.call(this,[引数[,引数[, ...,引数]]])
this の値と、個々にあたえた引数をわたして、関数を呼び出す。継承の場合は関数にスーパークラスを指定する。
引数 | this | 関数内の this に設定される値 |
---|---|---|
引数 | 関数に渡される引数 | |
戻り値 | 関数値 |
などのブラウザでは、他のオブジェクト指向プログラミング言語と同じような「クラス」が実装されています。
しかし、古いブラウザでは、「クラス」が存在しないものもあります。代わりに C++、Java などにはない prototype や C++、Java のとは全く異なる new 演算子や this が用意されています。
ここでは、class を使用した方法と class のないブラウザで使用される方法について説明します。また、他の方法についても「7.3 その他のオブジェクト指向プログラミング」にまとめて掲載しています。
新しいブラウザでは、他のオブジェクト指向プログラミング言語と同じような「クラス」が実装されています。
class を用いて「クラス」を定義します。
constructor は、インスタンス生成時に実行される関数です。インスタンス生成時に何も実行することがなければ記述する必要はありません。Java では、クラス名と同じ名前の関数として定義していました。
class クラス名 { constructor([引数]){ // コンストラクタ 文; } // メンバ関数 }
例えば、Bird というクラスを定義するには次のようにします。
<script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } fly() { return "バサバサ"; } } </script>
this.kind は、メンバ変数です。this. を付ければ定義しなくても使用することができます。
class を用いて定義されたクラスのインスタンスを作成する場合には new 演算子を使用します。
具体的には、constructor を実行します。return 文がなくてもインスタンスが返されます。
var 変数名 = new クラス名([引数[,引数[, ...,引数]]]);
次の例では、Bird() を実行して、インスタンスを生成します。
<div id="d1"></div> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } fly() { return "バサバサ"; } } var bird = new Bird(); // インスタンスの作成 document.getElementById('d1').textContent = bird.kind + "が" + bird.fly() + "と羽ばたく"; </script>
constructor() が実行され、this.kind に "鳥" が設定されます。このときの this は、bird インスタンスが設定されています。
メンバ変数は次のように宣言します。
this.メンバ変数名[ = 初期値];
<div id="d1"></div> <div id="d2"></div> <script> class Bird { constructor() { this.kind = "鳥"; } } var bird = new Bird(); var bird2 = new Bird(); document.getElementById('d1').textContent = bird.kind; document.getElementById('d2').textContent = bird2.kind; </script>
bird も bird2 もインスタンス生成時に constructor が実行されるので kind に "鳥" が代入されます。
なお、メンバ変数は当然のことながら値を変更することができます。
しかし、メンバ変数はインスタンスごと別の領域として確保されますので、bird.kind の変更が bird2.kind の変更になってしまうということはありません。
<div id="d1"></div> <div id="d2"></div> <script> class Bird { constructor() { this.kind = "鳥"; } } var bird = new Bird(); var bird2 = new Bird(); bird.kind = "ニワトリ"; // bird の kind を ニワトリ に変更 document.getElementById('d1').textContent = bird.kind; document.getElementById('d2').textContent = bird2.kind; // bird2 の kind は変更されない </script>
その他に、メンバ変数に値を代入することでメンバ変数を定義することもできます。以下のようにして、bird という名前のインスタンスを作成し、bird.voice という名前のメンバ変数を追加します。
ただし、この方法で voice というメンバ変数が追加されるのはあくまで bird インスタンスです。つまり、Bird クラスには追加されていないので bird2 インスタンスを作成したとしても、voice というメンバ変数は存在していません。
また、文字列を キー とする連想配列のようにオブジェクトを使い、bird["voice"] という形式でも参照(もちろん代入も)することができます。
<div id="d1"></div> <div id="d2"></div> <div id="d3"></div> <script> class Bird { constructor() { this.kind = "鳥"; } } var bird = new Bird(); bird.voice = "..."; // bird インスタンスに voice を追加 var bird2 = new Bird(); document.getElementById('d1').textContent = bird.kind + " " + bird.voice; document.getElementById('d2').textContent = bird2.kind + " " + bird2.voice; // bird2 インスタンスには voice はない document.getElementById('d3').textContent = bird["kind"] + " " + bird["voice"]; // 連想配列のようにも指定できる </script>
kind は、Bird の prototype を参照しているので、bird も bird2 も "鳥" と表示することはできますが、voice は bird2 には存在していないので、undefined と表示されます。
メンバ関数は class 内に通常の関数のように宣言します。ただし、function キーワードを付けてはいけません。
class クラス名 { constructor([引数]){ // コンストラクタ 文; } // function キーワードは無し メンバ関数名([引数[,引数[, ...,引数]]]) { 文; } // 関数式でも可能 メンバ関数名 = function([引数[,引数[, ...,引数]]]) { 文; } }
<div id="d1"></div> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } } var bird = new Bird(); document.getElementById('d1').textContent = bird.kind + " " + bird.sing(); </script>
メンバ変数と同様、インスタンスにメンバ関数を定義することもできます。
やはり、この方法では fly() というメンバ関数が追加されるのはあくまで bird インスタンスです。つまり、Bird クラスには追加されていないので bird2 インスタンスを作成したとしても、fly() というメンバ関数は存在していません。
<div id="d1"></div> <div id="d2"></div> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } } var bird = new Bird(); bird.fly = function() { return "バサバサ"; } // bird インスタンスに fly() を追加 var bird2 = new Bird(); document.getElementById('d1').textContent = bird.kind + " " + bird.sing() + " " + bird.fly(); try { document.getElementById('d2').textContent = bird2.kind + " " + bird2.sing() + " " + bird2.fly(); } catch(e) { document.getElementById('d2').textContent = e.message; // bird2.fly() がないので例外が発生する } </script>
constructor 関数はクラスで作成されたインスタンスの生成と初期化のための特殊な関数です。クラス内に1つだけ記述することができます。JavaScript では new 演算子を使うことで constructor 関数が呼び出されます。
他のクラスを継承して作成した「サブクラス」の場合に、「スーパークラス」の constructor 関数を呼び出したければ、自クラスのコンストラクタ内に super 関数を記述します。詳しくは次の「7.1.6 継承」を参照してください。
独自の constructor 関数を記述しなかった場合は、既定の constructor 関数が実行されます。自クラスが他のクラスを継承していない「スーパークラス」の場合、コンストラクタでは何もしません。他のクラスを継承した「サブクラス」の場合は「スーパークラス」の constructor 関数が呼び出されます。
また new で生成されたインスタンスは、constructor というプロパティに、生成時に利用されたクラスへの参照を保持しています。これを利用してどのクラスのインスタンスかを判定することができます。
プロパティ | 値 | ||||||||
---|---|---|---|---|---|---|---|---|---|
constructor | インスタンスのコンストラクタ関数への参照 また、次のようなプロパティを持っている
|
次の例では、new Bird() によって新しいインスタンスが生成されます。
<div id="d1"></div> <!-- 以下略 --> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } fly() { return "バサバサ"; } } var bird = new Bird(); document.getElementById('d1').textContent = bird.constructor; document.getElementById('d2').textContent = bird.constructor.length; document.getElementById('d3').textContent = bird.constructor.name; document.getElementById('d4').textContent = bird.constructor.prototype; </script>
クラスに「親子関係」を持たせることを「継承」といいます。子供のクラスは親クラスの性質を受け継ぐことができます。プログラミング言語の場合、引き継ぐ性質は定義されたメンバ変数 (プロパティ) やメンバ関数(メソッド)などになります。
つまり、既存のクラスの構造をベースに新しいクラスを定義する、クラスの全てを再利用する仕組みです。
クラスを継承する場合、その親になるクラスを「スーパークラス」と呼びます。そして、継承した子供のクラスを「サブクラス」と呼びます。ただし、この呼び方は言語によってまちまちで統一されていません。C++ の場合は、元になるクラスを「基本クラス」といい、継承するクラスを「派生クラス」ともいいます。
class を用いてクラスを作成した場合、extends を用いて、サブクラスを作成することができます。
サブクラスのコンストラクタ内で、super 関数を呼び出すとスーパークラスの constructor 関数を実行することができます。また、super.メンバ関数() でスーパークラスのメンバ関数の実行を、super.メンバ変数 でメンバ変数の参照をすることができます。
class サブクラス名 extends スーパークラス名 { constructor([引数]) { // コンストラクタ super(); // スーパークラスの constructor() の呼び出し 文; } 関数名([引数]) { // メンバ関数 super.メンバ関数(); // スーパークラスのメンバ関数の呼び出し super.メンバ変数; // スーパークラスのメンバ変数の参照 文; } }
また、サブクラスでスーパークラスのメンバー関数を置き換えることができます。これをオーバーライドといいます。
Cock で sing をオーバーライドしていますので、cock.sing() では Cock で定義された sing() が呼ばれます。しかし、Cock では fly は 定義されていませんので、cock.fly() では スーパークラス Bird の fly() が呼ばれます。
<div id="d1"></div> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } fly() { return "バサバサ"; } } class Cock extends Bird { // Bird を継承する constructor() { super(); this.kind = "ニワトリ"; } sing() { return "コケコッコー"; } // sing をオーバーライドする } var cock = new Cock(); document.getElementById('d1').textContent = cock.kind + " " + cock.sing() + " " + cock.fly(); </script>
「委譲」とは、メンバ関数の処理を別のオブジェクトに委ねることで別のオブジェクトのメンバ関数を再利用する手法のことです。
再利用の仕組みとしては、先の継承がありますが、継承はスーパークラスの構造をすべて受け継ぎます。したがって、スーパークラスの変更はすべてのサブクラスに影響を与えてしまいます。それに対して、委譲はクラスの一部のメンバ関数のみを再利用します。したがって、委譲先のクラスの構造に関して知っている必要はなく、結果的に他のクラスの変更に影響を受けにくい手法となっています。
Penguin はペンギンなので飛べません。fly() をオーバーライドしています。
CockPenguin はニワトリの鳴き声をするペンギンです。sing() の処理は Cock クラスの sing() に委譲しています。
<div id="d1"></div> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } fly() { return "バサバサ"; } } class Cock extends Bird { // Bird を継承する constructor() { super(); this.kind = "ニワトリ"; } sing() { return "コケコッコー"; } // sing をオーバーライドする } class Penguin extends Bird { // Bird を継承する constructor() { super(); this.kind = "ペンギン"; } fly() { return "ドテッ";} // fly をオーバーライドする } class CockPenguin extends Penguin { // Penguin を継承する constructor() { super(); this.kind = "ニワトリペンギン"; this.cock = new Cock(); } sing() { return this.cock.sing();} // cock に処理を委譲する } var cockPenguin = new CockPenguin(); document.getElementById('d1').textContent = cockPenguin.kind + " " + cockPenguin.sing() + " " + cockPenguin.fly(); </script>
sing() を Cock に委譲してニワトリの鳴き声をさせることができました。ただし、これではカラスの鳴き声をさせたければ sing() を Crow に委譲した CrowPenguin を作成する必要があります。つまり、鳴き声の種類だけクラスを作らなければなりません。
そこで、どのような鳴き声でもさせることができる、SingingPenguin を作成することにします。これは SingingPenguin のコンストラクタで受け取った Bird のサブクラスに sing() の処理を委譲しています。
スーパークラス型の変数にサブクラスのオブジェクトを代入しメンバ関数を呼び出すとサブクラスのメンバ関数が実行されるという手法はポリモーフィズムとして知られています。この例でもポリモーフィズムを実現しているのですが、Javascript では変数に型がないので実際にはどのようなクラスのメソッドでも呼び出せます。
<div id="d1"></div> <div id="d2"></div> <script> class Bird { constructor() { this.kind = "鳥"; } sing() { return "..."; } fly() { return "バサバサ"; } } class Cock extends Bird { // Bird を継承する constructor() { super(); this.kind = "ニワトリ"; } sing() { return "コケコッコー"; } // sing をオーバーライドする } class Crow extends Bird { // Bird を継承する constructor() { super(); this.kind = "カラス"; } sing() { return "カーカー";} // sing をオーバーライドする } class Penguin extends Bird { // Bird を継承する constructor() { super(); this.kind = "ペンギン"; } fly() { return "ドテッ";} // fly をオーバーライドする } class SingingPenguin extends Penguin { constructor(bird) { super(); this.kind = "シンギングペンギン"; this.bird = bird; } sing() { return this.bird.sing();} // 引数の bird に処理を委譲する } var singingPenguin; singingPenguin = new SingingPenguin(new Cock()); document.getElementById('d1').textContent = singingPenguin.kind + " " + singingPenguin.sing() + " " + singingPenguin.fly(); singingPenguin = new SingingPenguin(new Crow()); document.getElementById('d2').textContent = singingPenguin.kind + " " + singingPenguin.sing() + " " + singingPenguin.fly(); </script>
古いブラウザなどでは、「クラス」が存在しませんが、function を用いて「クラス」を定義することができます。
また、この方法は新しいブラウザを含めたほとんどのブラウザで動作します。
function を用いて「クラス」を定義します。これは通常の関数を定義する構文と同一です。
そのため、クラスとして定義した場合は慣習として1文字目は大文字にして、普通の関数と区別します。
function クラス名([引数[,引数[, ...,引数]]]) {}
例えば、Bird というクラスを定義するには次のようにします。
function Bird() {}
クラスのメンバ変数やメンバ関数は、クラス名.prototype に定義します。
prototype は、すべてのクラスが持っているプロパティです。クラスを定義した直後は空のインスタンスを参照していますが、別のインスタンスを代入したり、新たなプロパティを設定したりすることも可能です。
プロパティ | 値 |
---|---|
prototype | プロトタイプオブジェクトへの参照 |
function Bird() {} Bird.prototype.kind = "鳥"; // メンバ変数 Bird.prototype.fly = function() { return "バサバサ"; } // メンバ関数
上記の例では次のような状態になっています。
なお、メンバ変数やメンバ関数の定義を、クラス定義の中で行うことも可能ですが、クラス定義の内部で行うとインスタンスの生成のたびにメンバ変数やメンバ関数の定義が実行されてしまいます。そのため、通常はクラス定義の外に記述します。
function を用いて定義されたクラスのインスタンスを作成する場合には new 演算子を使用します。
具体的には、クラス名に記述された関数を実行します。return 文がなくてもインスタンスが返されます。
var 変数名 = new クラス名([引数[,引数[, ...,引数]]]);
次の例では、Bird() を実行して、インスタンスを生成します。
<div id="d1"></div> <script> function Bird() {} Bird.prototype.kind = "鳥" ; Bird.prototype.fly = function() { return "バサバサ"; } var bird = new Bird(); // インスタンスの作成 document.getElementById('d1').textContent = bird.kind + "が" + bird.fly() + "と羽ばたく"; </script>
メンバ変数は次のように宣言します。
クラス名.prototype.メンバ変数名[ = 初期値];
<div id="d1"></div> <div id="d2"></div> <script> function Bird() {} Bird.prototype.kind = "鳥"; var bird = new Bird(); var bird2 = new Bird(); document.getElementById('d1').textContent = bird.kind; document.getElementById('d2').textContent = bird2.kind; </script>
kind は、Bird の prototype を参照しているので、bird も bird2 も "鳥" と表示します。
なお、メンバ変数は当然のことながら値を変更することができます。
しかし、new を実行して生成された bird や bird2 が共に同じ prototype への参照を持つのであれば、例えば bird.kind の変更は bird2.kind の変更になってしまうような気がします。
しかし、prototype は確かに bird と bird2 で共有されていますが、実際には書き換えられてしまうということはありません。なぜなら、bird.kind を変更した時点で、bird 内に kind が新たに生成され、そこが書き換えられるからです。
<div id="d1"></div> <div id="d2"></div> <script> function Bird() {} Bird.prototype.kind = "鳥"; var bird = new Bird(); var bird2 = new Bird(); bird.kind = "ニワトリ"; // bird の kind を ニワトリ に変更 document.getElementById('d1').textContent = bird.kind; document.getElementById('d2').textContent = bird2.kind; // bird2 の kind は変更されない </script>
その他に、メンバ変数に値を代入することでメンバ変数を定義することもできます。以下のようにして、bird という名前のインスタンスを作成し、bird.voice という名前のメンバ変数を追加します。
ただし、この方法で sing というメンバ変数が追加されるのはあくまで bird インスタンスです。つまり、Bird クラスには追加されていないので bird2 インスタンスを作成したとしても、sing というメンバ変数は存在していません。
また、文字列を キー とする連想配列のようにオブジェクトを使い、bird["sing"] という形式でも参照(もちろん代入も)することができます。
<div id="d1"></div> <div id="d2"></div> <div id="d3"></div> <script> function Bird() {} Bird.prototype.kind = "鳥"; var bird = new Bird(); bird.voice = "..."; // bird インスタンスに voice を追加 var bird2 = new Bird(); document.getElementById('d1').textContent = bird.kind + " " + bird.voice; document.getElementById('d2').textContent = bird2.kind + " " + bird2.voice; // bird2 インスタンスには voice はない document.getElementById('d3').textContent = bird["kind"] + " " + bird["voice"]; // 連想配列のようにも指定できる </script>
kind は、Bird の prototype を参照しているので、bird も bird2 も "鳥" と表示することはできますが、voice は bird2 には存在していないので、undefined と表示されます。
メンバ関数は次のように宣言します。
クラス名.prototype.メンバ関数名 = function([引数[,引数[, ...,引数]]]) {
文;
}
<div id="d1"></div> <script> function Bird() {} Bird.prototype.kind = "鳥" ; Bird.prototype.sing = function() { return "..."; } var bird = new Bird(); document.getElementById('d1').textContent = bird.kind + " " + bird.sing(); </script>
メンバ変数と同様、インスタンスにメンバ関数を定義することもできます。
やはり、この方法では fly() というメンバ関数が追加されるのはあくまで bird インスタンスです。つまり、Bird クラスには追加されていないので bird2 インスタンスを作成したとしても、fly() というメンバ関数は存在していません。
<div id="d1"></div> <div id="d2"></div> <script> function Bird() {} Bird.prototype.kind = "鳥" ; Bird.prototype.sing = function() { return "..."; } var bird = new Bird(); bird.fly = function() { return "バサバサ"; } // bird インスタンスに fly() を追加 var bird2 = new Bird(); document.getElementById('d1').textContent = bird.kind + " " + bird.sing() + " " + bird.fly(); try { document.getElementById('d2').textContent = bird2.kind + " " + bird2.sing() + " " + bird2.fly(); } catch(e) { document.getElementById('d2').textContent = e.message; // bird2.fly() がないので例外が発生する } </script>
JavaScript では new 演算子を使うことで関数を「コンストラクタ」として利用することができます。
また new で生成されたインスタンスは、constructor というプロパティに、生成時に利用されたクラスへの参照を保持しています。これを利用してどのクラスのインスタンスかを判定することができます。
プロパティ | 値 | ||||||||
---|---|---|---|---|---|---|---|---|---|
constructor | インスタンスのコンストラクタ関数への参照 また、次のようなプロパティを持っている
|
次の例では、new Bird() によって新しいインスタンスが生成されます。
<div id="d1"></div> <!-- 以下略 --> <script> function Bird() {} Bird.prototype.kind = "鳥" ; Bird.prototype.sing = function() { return "..."; } Bird.prototype.fly = function() { return "バサバサ"; } var bird = new Bird(); document.getElementById('d1').textContent = bird.constructor; document.getElementById('d2').textContent = bird.constructor.length; document.getElementById('d3').textContent = bird.constructor.name; document.getElementById('d4').textContent = bird.constructor.prototype; </script>
クラスに「親子関係」を持たせることを「継承」といいます。子供のクラスは親クラスの性質を受け継ぐことができます。プログラミング言語の場合、引き継ぐ性質は定義されたメンバ変数 (プロパティ) やメンバ関数(メソッド)などになります。
つまり、既存のクラスの構造をベースに新しいクラスを定義する、クラスの全てを再利用する仕組みです。
クラスを継承する場合、その親になるクラスを「スーパークラス」と呼びます。そして、継承した子供のクラスを「サブクラス」と呼びます。ただし、この呼び方は言語によってまちまちで統一されていません。C++ の場合は、元になるクラスを「基本クラス」といい、継承するクラスを「派生クラス」ともいいます。
JavaScript の場合、Object.setPrototypeOf() を用いて、サブクラスを作成することができます。
Object.setPrototypeOf(サブクラス.prototype, スーパークラス.prototype); // prototype をコピーしてスーパークラスを継承
また、サブクラスでスーパークラスのメンバー関数を置き換えることができます。これをオーバーライドといいます。
Cock で sing をオーバーライドしていますので、cock.sing() では Cock で定義された sing() が呼ばれます。しかし、Cock では fly は 定義されていませんので、cock.fly() では setPrototypeOf によって Bird から Cock にコピーされた fly() が呼ばれます。
<div id="d1"></div> <script> function Bird() {} Bird.prototype.kind = "鳥"; Bird.prototype.sing = function() { return "..."; } Bird.prototype.fly = function() { return "バサバサ"; } function Cock() {} Object.setPrototypeOf(Cock.prototype, Bird.prototype); // Bird を継承する Cock.prototype.kind = "ニワトリ"; Cock.prototype.sing = function() { return "コケコッコー"; } // sing をオーバーライドする var cock = new Cock(); document.getElementById('d1').textContent = cock.kind + " " + cock.sing() + " " + cock.fly(); </script>
「委譲」とは、メンバ関数の処理を別のオブジェクトに委ねることで別のオブジェクトのメンバ関数を再利用する手法のことです。
再利用の仕組みとしては、先の継承がありますが、継承はスーパークラスの構造をすべて受け継ぎます。したがって、スーパークラスの変更はすべてのサブクラスに影響を与えてしまいます。それに対して、委譲はクラスの一部のメンバ関数のみを再利用します。したがって、委譲先のクラスの構造に関して知っている必要はなく、結果的に他のクラスの変更に影響を受けにくい手法となっています。
Penguin はペンギンなので飛べません。fly() をオーバーライドしています。
CockPenguin はニワトリの鳴き声をするペンギンです。sing() の処理は Cock クラスの sing() に委譲しています。
<div id="d1"></div> <script> function Bird() {} Bird.prototype.kind = "鳥"; Bird.prototype.sing = function() { return "..."; } Bird.prototype.fly = function() { return "バサバサ"; } function Cock() {} Object.setPrototypeOf(Cock.prototype, Bird.prototype); // Bird を継承する Cock.prototype.kind = "ニワトリ"; Cock.prototype.sing = function() { return "コケコッコー"; } // sing をオーバーライドする function Penguin() {} Object.setPrototypeOf(Penguin.prototype, Bird.prototype); // Bird を継承する Penguin.prototype.kind = "ペンギン"; Penguin.prototype.fly = function() { return "ドテッ"; } // fly をオーバーライドする function CockPenguin() { this.cock = new Cock(); } Object.setPrototypeOf(CockPenguin.prototype, Penguin.prototype); // Penguin を継承する CockPenguin.prototype.kind = "ニワトリペンギン"; CockPenguin.prototype.sing = function() { return this.cock.sing(); } // Cock に処理を委譲する var cockPenguin = new CockPenguin(); document.getElementById('d1').textContent = cockPenguin.kind + " " + cockPenguin.sing() + " " + cockPenguin.fly(); </script>
sing() を Cock に委譲してニワトリの鳴き声をさせることができました。ただし、これではカラスの鳴き声をさせたければ sing() を Crow に委譲した CrowPenguin を作成する必要があります。つまり、鳴き声の種類だけクラスを作らなければなりません。
そこで、どのような鳴き声でもさせることができる、SingingPenguin を作成することにします。これは SingingPenguin のコンストラクタで受け取った Bird のサブクラスに sing() の処理を委譲しています。
スーパークラス型の変数にサブクラスのオブジェクトを代入しメンバ関数を呼び出すとサブクラスのメンバ関数が実行されるという手法はポリモーフィズムとして知られています。この例でもポリモーフィズムを実現しているのですが、Javascript では変数に型がないので実際にはどのようなクラスのメソッドでも呼び出せます。
<div id="d1"></div> <div id="d2"></div> <script> function Bird() {} Bird.prototype.kind = "鳥"; Bird.prototype.sing = function() { return "..."; } Bird.prototype.fly = function() { return "バサバサ"; } function Cock() {} Object.setPrototypeOf(Cock.prototype, Bird.prototype); // Bird を継承する Cock.prototype.kind = "ニワトリ"; Cock.prototype.sing = function() { return "コケコッコー"; } // sing をオーバーライドする function Crow() {} Object.setPrototypeOf(Crow.prototype, Bird.prototype); // Bird を継承する Crow.prototype.kind = "カラス"; Crow.prototype.sing = function() { return "カーカー"; } // sing をオーバーライドする function Penguin() {} Object.setPrototypeOf(Penguin.prototype, Bird.prototype); // Bird を継承する Penguin.prototype.kind = "ペンギン"; Penguin.prototype.fly = function() { return "ドテッ"; } // fly をオーバーライドする function SingingPenguin(bird) { this.bird = bird; } Object.setPrototypeOf(CockPenguin.prototype, Penguin.prototype); // Penguin を継承する CockPenguin.prototype.kind = "シンギングペンギン"; CockPenguin.prototype.sing = function() { return this.bird.sing(); } // 引数の bird に処理を委譲する var singingPenguin; singingPenguin = new SingingPenguin(new Cock()); document.getElementById('d1').textContent = singingPenguin.kind + " " + singingPenguin.sing() + " " + singingPenguin.fly(); singingPenguin = new SingingPenguin(new Crow()); document.getElementById('d2').textContent = singingPenguin.kind + " " + singingPenguin.sing() + " " + singingPenguin.fly(); </script>
この章の最初にも記述しましたが、class や prototype を使用した上記の方法以外にも JavaScript ではいろいろな方法でクラスの継承関係を作成することができます。
call() や apply() を用いても、サブクラスを作成することができます。引数固定の場合は call()、可変個引数の場合は apply() を用います。どちらも第一引数は、関数内の this に設定される値です。もし、第一引数がオブジェクトでない場合は、例えば 10 ならば new Number(10)、"文字列" ならば new String("文字列") のように、暗黙的にオブジェクトに変換されます。ただし、null や undefined の場合は 関数内の this にはグローバルオブジェクトである Window オブジェクトが設定されます。
なお、これらのメソッドは古くからある仕様なのでたいていのブラウザで動作します。
this の値と、個々にあたえた引数をわたして、関数を呼び出す。継承の場合は関数にスーパークラスを指定する。
引数 | this | 関数内の this に設定される値 |
---|---|---|
引数 | 関数に渡される引数 | |
戻り値 | 関数値 |
this の値と、個々にあたえた引数のリストをわたして、関数を呼び出す。継承の場合は関数にスーパークラスを指定する。
引数 | this | 関数内の this に設定される値 |
---|---|---|
引数 | 関数に渡される引数のリスト | |
戻り値 | 関数値 |
スーパークラスを継承してサブクラスを作成する場合には次のように宣言します。
function サブクラス名([引数[,引数[, ...,引数]]]) { スーパークラス.call(this,[引数[,引数[, ...,引数]]]); // スーパークラスを継承 文; }
あるいは、
function サブクラス名() { スーパークラス.apply(this,arguments); // スーパークラスを継承 文; }
new を用いてオブジェクトを生成する際に、call や apply を用いることで Java のようにコンストラクタによる初期化を連鎖させることができます。
下の例では new Cock() を実行したときに Cock 内の Bird.call が実行され、Cock 内の this と "ニワトリ" を引数に Bird() が呼ばれます。よって、そのときの Bird() 内の this は、Bird.call したときの第一引数、つまり Cock 内の this(Cock のオブジェクト)になります。
そのため、Bird() 内の代入式の this.kind や this.sing、this.fly の this が Cock オブジェクトなので、kind や sing、fly は Cock オブジェクトに作られることになります。そして、その結果、Cock には定義されていない fly を利用できるようになるわけです。
<div id="d1"></div> <div id="d2"></div> <script> function Bird() { this.kind = (arguments[0] == undefined) ? "鳥" : arguments[0]; // 引数があれば設定する this.sing = function() { return "..."; } this.fly = function() { return "バサバサ"; } } function Cock() { Bird.call(this, "ニワトリ"); // Bird を継承する(Cock の this と "ニワトリ" を引数に Bird() を呼び出す) this.sing = function() { return "コケコッコー"; }// sing をオーバーライドする(sing の実行内容を換える) } function Crow() { Bird.call(this, "カラス"); // Bird を継承する this.sing = function() { return "カーカー"; } // sing をオーバーライドする } function Penguin() { var kind = (arguments[0] == undefined) ? "ペンギン" : arguments[0]; Bird.call(this, kind); // Bird を継承する this.fly = function() { return "ドテッ"; } // fly をオーバーライドする } function SingingPenguin(bird) { Penguin.call(this, "シンギングペンギン"); // Penguin を継承する this.sing = function() { return bird.sing(); } // 引数の bird に処理を委譲する } var singingPenguin; singingPenguin = new SingingPenguin(new Cock()); // Cock のオブジェクト document.getElementById('d1').textContent = singingPenguin.kind + " " + singingPenguin.sing() + " " + singingPenguin.fly(); singingPenguin = new SingingPenguin(new Crow()); // Crow のオブジェクト document.getElementById('d2').textContent = singingPenguin.kind + " " + singingPenguin.sing() + " " + singingPenguin.fly(); </script>
Object.create でスーパークラスのプロトタイプに新たなプロパティを追加したり、既存のプロパティを置き換えたりして、サブクラスを生成します。
<div id="d1"></div> <div id="d2"></div> <script> var Bird = Object.create({}, { kind : {value:"鳥"}, sing : {get:function() { return "..."; }}, fly : {get:function() { return "バサバサ"; }} }); var Cock = Object.create(Bird, { kind:{value:"ニワトリ"}, sing:{get:function() { return "コケコッコー"; }} }); var Crow = Object.create(Bird, { kind:{value:"カラス"}, sing:{get:function() { return "カーカー"; }} }); var Penguin = Object.create(Bird, { kind:{value:"ペンギン"}, fly:{get:function() { return "ドテッ"; }} }); var SingingPenguin = Object.create(Penguin, { kind:{value:"シンギングペンギン"}, singer:{set:function(b) { this.bird = b; }}, sing:{get:function() { return this.bird.sing; }} }); var singingPenguin = Object.create(SingingPenguin); singingPenguin.singer = Cock; // Cock のオブジェクト document.getElementById('d1').textContent = singingPenguin.kind + " " + singingPenguin.sing + " " + singingPenguin.fly; singingPenguin.singer = Crow; // Crow のオブジェクト document.getElementById('d2').textContent = singingPenguin.kind + " " + singingPenguin.sing + " " + singingPenguin.fly; </script>
通常のメンバ変数はオブジェクトごとに作成されるオブジェクト固有のものです。動的変数とも言います。それに対して、静的変数はオブジェクト間で共通で、クラスに1つしか存在しません。よって、あるオブジェクトが静的変数を変更すると他のオブジェクトから見ても値が変わってしまうことになります。
また、動的変数を参照するには動的メソッドとも呼ばれる通常のメンバ関数である必要がありますし、静的変数には静的メソッドが必要になります。
これもまた、いろいろな方法で作成することができます。
class が使用できるのならば、静的変数や静的メソッドは Java と同じように static 宣言をして定義します。呼び出すには、クラス名.静的変数名、クラス名.静的メソッド名() のように指定します。
また、静的変数は、クラス外に クラス名.静的変数名のような形式で宣言することもできます。
class クラス名 { static 静的変数名 = 初期値; // 静的変数の宣言 static 静的メソッド名() { return クラス名.静的変数名 } } クラス名.静的変数名 = 初期値; // この方法でも静的変数の宣言ができる
<div id="d1"></div> <script> class Bird { constructor(kind) { this.kind = (arguments[0] == undefined) ? "鳥" : arguments[0]; } sing() { return "..."; } fly() { return "バサバサ"; } } class Sparrow extends Bird { static count = 0; // 静的変数の宣言 constructor() { super("スズメ"); Sparrow.count++; } static getCount() { return Sparrow.count; } } var list = [new Sparrow(), new Sparrow(), new Sparrow(), new Sparrow()]; // Sparrow オブジェクトを4つ生成する document.getElementById('d1').textContent = Sparrow.getCount(); // Sparrow オブジェクトの数を得る </script>
callee はすべての関数オブジェクト内で利用できるローカル変数 arguments のプロパティであり、自分自身を参照することができます。
function クラス名() { arguments.callee.静的変数名 = 初期値; arguments.callee.静的メソッド名 = function() { return クラス名.静的変数名 } }
Sparrow は、オブジェクトが生成されるたびにその数を数えています。クラスに共通の領域でないとそれまで生成された数を覚えていられないので、静的変数にして数を数えます。またその値を返す関数も、静的メソッドである必要があります。
<div id="d1"></div> <script> function Bird() { Bird.prototype.kind = "鳥"; Bird.prototype.sing = function() { return "..."; } Bird.prototype.fly = function() { return "バサバサ"; } function Sparrow() { if (arguments.callee.count == undefined) // count が未定義のとき arguments.callee.count = 0; // 値 0 で、count を定義する arguments.callee.count++; // オブジェクト生成のたびに count を加算する arguments.callee.getCount = function() { return Sparrow.count; } } Object.setPrototypeOf(Sparrow.prototype, Bird.prototype); // Bird を継承する Sparrow.prototype.kind = "スズメ"; var list = [new Sparrow(), new Sparrow(), new Sparrow(), new Sparrow()]; // Sparrow オブジェクトを4つ生成する document.getElementById('d1').textContent = Sparrow.getCount(); // Sparrow オブジェクトの数を得る </script>
Safari は、setPrototypeOf が機能しないので表示されませんが、callee 自体は動作します。
JavaScript の this は同じソース上でも呼び出し元次第で意味が違います。
なお、以下の例では this の内容を表示するために dispThis という関数を作成しています。その関数は、constructor.name を表示していますが、 では、constructor.name が未定義なので constructor を表示しています。
トップレベル(どの関数にも属さない、すべての関数の外側)で使用された場合は、グローバルオブジェクトを指します。
Javascript では、Window オブジェクトがグローバルオブジェクトです。
<div id="d1"></div> <div id="d2"></div> <script> var value = "Global"; document.getElementById('d1').textContent = dispThis(this); // this は Window オブジェクト document.getElementById('d2').textContent = this.value; function dispThis(obj) { var name = obj.constructor.name; if (name != undefined && name != "") return name; return obj.constructor; } </script>
関数内では、this の値は関数の呼び出され方によって異なります。
なお、以下の例で使用されている dispThis という関数は、「7.5.1 グローバルコンテクスト」の例を参照してください。
オブジェクトのメソッドとしてではなく、単なる関数として呼び出されている場合は、this の値は呼び出しによって設定されません。this の値は常にグローバルオブジェクト、つまり Window オブジェクトです。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // window.value と記述しても同じ function func(){ var value = "local"; // ここで this.value とすると window.value の意味になる return this; // this は Window オブジェクト(= window) } document.getElementById('d1').textContent = dispThis(func()); document.getElementById('d2').textContent = func().value; </script>
オブジェクトのメソッドとして呼び出されている場合は、this は、生成されるオブジェクト(この例では Obj)への参照になります。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // window.value var Obj = function() { this.value = "local"; // Obj オブジェクト の value this.func = function(){ return this; // this は Obj オブジェクト } } var obj = new Obj(); document.getElementById('d1').textContent = dispThis(obj); document.getElementById('d2').textContent = obj.func().value; </script>
オブジェクトのメソッドから呼び出された関数の場合は、グローバルオブジェクトである Window オブジェクトです。つまり、Obj 内の this.value での this は、Obj オブジェクトですが、sub() で return している this は、Window オブジェクトです。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // this は Window オブジェクト var Obj = function() { this.value = "local"; // this は Obj オブジェクト this.func = function(){ var sub = function(){ return this; // this は Window オブジェクト } return sub(); // Window オブジェクトが返る } } var obj = new Obj(); document.getElementById('d1').textContent = dispThis(obj.func()); document.getElementById('d2').textContent = obj.func().value; </script>
obj.func() は Window オブジェクトを返すので、obj.func().value は Global になります。
obj.func().value が Obj オブジェクト内の value の値である local を返すようにするには、次のように this を別の変数に保存し、その変数を参照します。変数名としては、「self」、「that」、「_this」などが使われることが多いようです。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // this は Window オブジェクト (var value = "Global"; としても同じ意味) var Obj = function() { var self = this; // Obj オブジェクトを保存 this.value = "local"; // this は Obj オブジェクト this.func = function() { var sub = function(){ return self; // self は Obj オブジェクト } return sub(); } } var obj = new Obj(); document.getElementById('d1').textContent = dispThis(obj.func()); document.getElementById('d2').textContent = obj.func().value; </script>
call、apply では、this を外部から設定できます。したがって、this は call や apply されたときの引数の持つ値になります。
この例では、Obj1.call(this) によって、Obj1内の this に Obj2 のオブジェクトが設定され、Obj1 が実行されます。その結果、func 関数が Obj2 内に生成されます。そして、Obj2 オブジェクトの func 関数を呼び出せば、それが返す戻り値である this は Obj2 になります。
<div id="d1"></div> <div id="d2"></div> <script> function Obj1() { this.value = "local 1", this.func = function() { return this; } } function Obj2() { Obj1.call(this); this.value = "local 2"; } var obj = new Obj2(); // obj.func() は Obj2 オブジェクト document.getElementById('d1').textContent = dispThis(obj.func()); document.getElementById('d2').textContent = obj.func().value; </script>
プロトタイプでは、this は、生成されるオブジェクト(この例では Obj)への参照になります。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // this は Window オブジェクト function Obj() {} Obj.prototype.value = "local"; Obj.prototype.func = function() { return this; // this は Obj オブジェクト }; var obj = new Obj(); document.getElementById('d1').textContent = dispThis(obj.func()); document.getElementById('d2').textContent = obj.func().value; </script>
class 内で使用される this は、生成されるオブジェクト(この例では Obj)への参照になります。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // this は Window オブジェクト class Obj { constructor() { this.value = "local"; } func() { return this; // this は Obj オブジェクト } } var obj = new Obj(); document.getElementById('d1').textContent = dispThis(obj.func()); document.getElementById('d2').textContent = obj.func().value; </script>
また、サブクラスのオブジェクト(この例では obj2)から参照されたスーパークラス(この例では Obj)内の this は、サブクラス(この例では Obj2)のオブジェクトへの参照になります。
ただし、この例の Obj のようにスーパークラスとして記述されていても、そのクラスのオブジェクトを生成してそのオブジェクトから参照された場合の this は、そのクラス(この例では Obj)のオブジェクトへの参照になります。これは、上の例そのものです。
<div id="d1"></div> <div id="d2"></div> <script> this.value = "Global"; // this は Window オブジェクト class Obj { constructor() { this.value = "local"; } func() { return this; // obj2 から参照された場合の this は Obj2 オブジェクト } } class Obj2 extends Obj { // Obj を継承 constructor() { super(); this.value = "local2"; // this は Obj2 オブジェクト } } var obj2 = new Obj2(); document.getElementById('d1').textContent = dispThis(obj2.func()); document.getElementById('d2').textContent = obj2.func().value; </script>
アロー関数 では、this は常に Window オブジェクトです。
<span id="d11"></span> <span id="d12"></span><br> <span id="d21"></span> <span id="d22"></span> <script> var value = "Global"; var func1 = () => { return this; }; // this は Window オブジェクト var Obj = { value: "local", func2: () => { return this; } // this は Window オブジェクト }; document.getElementById('d11').textContent = dispThis(func1()); document.getElementById('d12').textContent = func1().value; document.getElementById('d21').textContent = dispThis(Obj.func2()); document.getElementById('d22').textContent = Obj.func2().value; </script>
コールバック関数では、this は常に Window オブジェクトです。
オブジェクトのメソッドも、オブジェクトのメソッドとして呼び出されている場合は、this は、生成されるオブジェクト(この例では Obj)への参照になりますが、コールバック関数として使用された場合は Window オブジェクトです。
<span id="d11"></span> <span id="d12"></span><br> <span id="d21"></span> <span id="d22"></span> <script> var value = "Global"; function func1(f) { return f(); } function func2() { var value = "local"; return this; // コールバックされた場合 this は Window オブジェクト } var Obj = function() { this.value = "local"; this.func = function() { return this; // コールバックされた場合 this は Window オブジェクト } } var obj = new Obj(); document.getElementById('d11').textContent = dispThis(func1(func2)); document.getElementById('d12').textContent = func1(func2).value; document.getElementById('d21').textContent = dispThis(func1(obj.func)); document.getElementById('d22').textContent = func1(obj.func).value; </script>
イベントハンドラは、イベントを検出し、イベントに処理を与えるのに使用します。 発生したイベントごとに指定された関数が呼び出され、処理を実行させることができます。
HTML の要素に、on~ 属性でイベントハンドラを記述したときには、その属性値内の this は、記述した要素のオブジェクトになります。
<input id="b" type="button" value="input" onclick="func(this)"> <!-- this は input 要素のオブジェクト --> <div id="d1"></div> <div id="d2"></div> <script> var value = "Global"; var element = document.getElementById('b'); function func(obj) { var value = "local"; document.getElementById('d1').textContent = dispThis(obj); // obj(==this) は input 要素のオブジェクト document.getElementById('d2').textContent = obj.value; } </script>
[input]ボタンをクリックしてください。
この場合の this は、クリックした input 要素であるボタンのオブジェクトになります。したがって、その this を受け取った obj の メンバーである obj.value は、input 要素の value 属性、つまり、"input" になります。
HTML の要素に、on~ 属性でイベントハンドラとして設定した関数においては、その関数内の this にはグローバルオブジェクトである Window オブジェクトが設定されます。
次の例は、ボタンをクリックしたときに呼び出されるイベントハンドラ(func)内の this は、Window オブジェクトを持っています。
<input type="button" value="input" onclick="func()"> <div id="d1"></div> <div id="d2"></div> <script> var value = "Global"; function func() { var value = "local"; document.getElementById('d1').textContent = dispThis(this); // this は Window オブジェクト document.getElementById('d2').textContent = this.value; } </script>
「input」ボタンをクリックしてください。
この場合の this は、Window オブジェクトになります。
要素オブジェクト.on~ に関数を代入して、イベントハンドラとして設定した場合は、イベントハンドラ内の this には要素オブジェクトが設定されます。
次の例は、ボタンをクリックしたときに呼び出されるイベントハンドラ(func)内の this は、クリックした input 要素のオブジェクトです。
<input id="b" type="button" value="input"> <div id="d1"></div> <div id="d2"></div> <script> var value = "Global"; var element = document.getElementById("b"); element.onclick = func; function func() { var value = "local"; document.getElementById('d1').textContent = dispThis(this); // this は input 要素のオブジェクト document.getElementById('d2').textContent = this.value; } </script>
[input]ボタンをクリックしてください。
この場合の this は、クリックした input 要素であるボタンのオブジェクトになります。したがって、this.value は、input 要素の value 属性、つまり、"input" になります。
addEventListener メソッドで、イベントハンドラとして要素に付加した関数においては、イベントハンドラ内の this にはイベントを発生させた要素が設定されます。
次の例は、ボタンをクリックしたときに呼び出されるイベントハンドラ(func)内の this は、クリックした input 要素のオブジェクトです。
<input id="b" type="button" value="input"> <div id="d1"></div> <div id="d2"></div> <script> var value = "Global"; var element = document.getElementById('b'); element.addEventListener('click', func, false); function func() { var value = "local"; document.getElementById('d1').textContent = dispThis(this); // this は input 要素のオブジェクト document.getElementById('d2').textContent = this.value; } </script>
[input]ボタンをクリックしてください。
この場合の this は、クリックした input 要素のであるボタンのオブジェクトになります。したがって、this.value は、input 要素の value 属性、つまり、"input" になります。