(2017.7.30) 現代的な内容に更新。
ECMAScript 2015 (ES6) で 'class
' キーワードが導入された。糖衣構文 syntactic sugar で、本質的に新しい機能が入ったわけではない。
5th edition 水準の Internet Explorer 11 (IE11) のサポート期間は, Windows 7用が2020年1月まで, Windows 10用が2025年10月まである。少なくとも Windows 7がリタイアする2020年までは、そのままは使えない。
Babel のような変換コンパイラを使う手もあるが, 当面の間は無理せず 5th edition のレベルの書き方でいくのがいい,か。
ES6でのクラス
まず、ES6での書き方を示す。2017年7月現在、Webブラウザでのサポート範囲を考えると、まだ、バリバリ使えない。
だいぶclassベースのオブジェクト指向プログラミング言語に近い書き方。
JavaScript
- "use strict";
-
-
- var animalCount = 0;
-
- class Animal {
-
- constructor(kind) {
- this._kind = kind;
- animalCount += 1;
- }
-
- speak(cry) {
-
- alert( (this._name ? this._name : this._kind) + ' cries ' + cry );
- }
-
-
- static hasName(animal) {
-
-
- if (!(animal instanceof Animal))
- throw new TypeError();
- return !!animal._name;
- }
-
- static count() {
- return animalCount;
- }
-
-
- set name(name) {
- this._name = name + "-san";
- }
- }
構築子 (コンストラクタ) は constructor()
で固定。静的メソッドも static
で作れる。get
または set
でアクセサを作れる。
継承したサブクラスを作る。
JavaScript
-
- class Cat extends Animal {
- constructor(color) {
- if (color === undefined)
- throw new Error('color need');
-
-
- super('cat');
- this._color = color;
- }
-
- meow() {
-
- this.speak('みゃーー');
-
-
- alert('color is ' + this._color );
- }
- }
-
-
- var myCat = new Cat('red');
-
-
- document.write( Cat.hasName(myCat) );
- myCat.meow();
- myCat.name = "taro";
- document.write( Cat.hasName(myCat) );
- myCat.meow();
- document.write( Animal.count() );
コンストラクタといっても、糖衣構文で実態はただの関数なので、super()
で明示的に基底クラスのコンストラクタを呼び出さないといけない。
メソッドをオーバーライドした場合で、基底クラスのメソッドを呼び出したいときは, super.メソッド名()
でよい。
ES5: コンストラクタ, メソッド定義
ここからは, 'class
' キーワードを使わずに、ECMAScript 5 の範囲で、上のクラスを再現していく。
まずは基底クラス。
JavaScript
- "use strict";
-
- var animalCount = 0;
-
-
- function Animal(kind) {
- if (this === undefined)
- throw new TypeError();
-
- this._kind = kind;
- animalCount += 1;
- }
-
-
- Animal.prototype = {
- constructor: Animal,
-
- speak: function(cry) {
- alert( (this._name ? this._name : this._kind) + ' cries ' + cry );
- },
-
-
- set name(name) {
- this._name = name + "-san";
- }
- };
-
-
-
- Animal.hasName = function(animal) {
- if (!(animal instanceof Animal))
- throw new TypeError();
- return !!animal._name;
- }
-
- Animal.count = function() {
- return animalCount;
- }
-
-
- var ani = new Animal('dog');
- document.write( Animal.hasName(ani) );
- ani.speak('bow-wow');
- ani.name = 'shiro';
- document.write( Animal.hasName(ani) );
- ani.speak('bow-wow');
- document.write( Animal.count() );
JavaScript では, コンストラクタはただの関数。コンストラクタの関数を new
してオブジェクトを生成する。new
せずに直接呼び出せてしまう。このときは this
が undefined
になるので、エラーにする。
メソッドは, コンストラクタの関数オブジェクトの prototype
プロパティ値に設定する。コンストラクタを prototype.constructor
に設定するお約束。
アクセサは ES5から使える。
静的メソッドは, コンストラクタのプロパティとして設定する。
サンプル内で使っている instanceof
で、オブジェクトがどのクラス (=コンストラクタ) から作られたか判定できる。挙動については後述。
継承
prototype
プロパティ, setPrototypeOf()
を使って, クラスの継承のようなものを作る。
基本的に、テストは IE11とそれ以外で行うようにする。
今回の解説のキモは、この class_inherits
に集中している。
JavaScript
-
- function class_inherits(subClass, superClass) {
- if (typeof superClass !== "function" && superClass !== null) {
- throw new TypeError("superClass must be null or a constructor");
- }
-
-
-
-
-
-
-
- subClass.prototype = Object.create(superClass.prototype, {
- constructor: { value: subClass,
- enumerable: false,
- writable: true,
- configurable: true }
- });
-
-
-
- if (Object.setPrototypeOf)
- Object.setPrototypeOf(subClass, superClass);
- else
- subClass.__proto__ = superClass;
- }
現代の標準的なやり方では Object.create(orig [, Properties])
を使う。この関数は,
- 空のオブジェクトを作る => new_obj とする
- new_obj の [[Prototype]] 内部プロパティに orig をセットする。orig.prototype ではない。
- 引数 Properties がある場合, new_obj のプロパティに追加する。
- 戻り値は new_obj
第一引数をコピーするのではなく, [[Prototype]] 内部プロパティ (= __proto__
) に設定するのがミソ。このプロトタイプチェインによって、メソッドのオーバーライドや, instanceof
による判定が実現できる。
非常に混乱しやすいのは、prototype
プロパティと [[Prototype]] 内部プロパティが別物というところ。前者は新しいオブジェクトのためのテンプレートであり、後者はオブジェクトのプロパティが存在しない場合の辿っていく先であり, __proto__
でアクセスできる。
上のスクリプトに話を戻して, constructor
をサブクラスのものに差し替えるのを忘れずに。
静的メソッドの継承は, コンストラクタ関数オブジェクト自身がメソッド呼び出しの際のレシーバ (this
) になるので、そのメソッドの辿り先は、スーパークラス関数オブジェクトになる。
では、これを利用して、派生クラスを作ってみる。
JavaScript
-
-
-
- function Cat(color) {
- if (this === undefined)
- throw new TypeError();
- if (color === undefined)
- throw new Error('color need');
-
-
- Animal.call( this, "neko" );
- this._color = color;
- }
-
-
- class_inherits(Cat, Animal);
-
-
- Cat.prototype.meow = function() {
-
-
- this.speak('みゃーー');
-
- alert('color is ' + this._color );
- }
-
-
-
-
-
- var myCat = new Cat('white');
-
-
- document.write( Cat.hasName(myCat) );
- myCat.meow();
- myCat.name = "taro";
- document.write( Cat.hasName(myCat) );
- myCat.meow();
- document.write( Cat.count() );
myCatオブジェクトは, 次のようなプロトタイプチェインを持つ;
JavaScript
- {
- _kind: "neko", _color: "white", _name: "taro-san",
- __proto__: {
-
- meow: function (),
- constructor: function Cat(color),
- __proto__: {
-
- constructor: function Animal(kind),
- speak: function (cry),
- set name: function name(name),
- __proto__: { 以下略 }
- }
- }
- }
これを実現するために, class_inherits() 内で, subClass.prototype.__proto__
に superClass.prototype
を設定する必要があった。
obj instanceof Func
は, プロトタイプチェインを順に辿って, Func.prototype と一致するかを調べていく。