JavaScript のオブジェクト作成においてクラス定義で継承を実装する方法はいくつかあります。
正しい継承はどうあるべきか、基本から検証しながら考えてみたいと思います。
※正しくクラス定義がエコ楽にできる様に追加記事書きました。
[[JavaScript] getter/setterも使えるエコ楽なクラス定義 - もちろん継承も - private変数も]
(http://qiita.com/LightSpeedC/items/3946088b58925234cc48)
一番簡単なオブジェクトの作成方法
典型的な JavaScript のオブジェクトを簡単に作成してみて、それらを確認してみましょう。
var obj1 = {x: 12, y: "ab"};
var obj2 = new Object; // または new Object()
obj2.x = 34;
obj2.y = "cd";
// obj < Object
var obj3 = [12, "ab"];
var obj4 = new Array(34, "cd");
// obj < Array < Object
instanceof
による確認
instanceof
を使用すると Object
かどうかがすぐにわかります。
console.log(obj1 instanceof Object); // -> true
console.log(obj2 instanceof Object); // -> true
console.log(obj3 instanceof Object); // -> true
console.log(obj4 instanceof Object); // -> true
true
なので、全て Object
の一種です。間違いありません。
constructor
による確認
constructor
を使用すると、オブジェクトが誰によって作成されたのか、つまりどのクラスのオブジェクトなのか確認することができます。
console.log(obj1.constructor === Object); // -> true
console.log(obj2.constructor === Object); // -> true
console.log(obj3.constructor === Array); // -> true
console.log(obj4.constructor === Array); // -> true
後半の例は配列 Array
です。思った通りですね。
もう一度 instanceof
による確認
それでは Array
なのかどうか instanceof
で確認してみましょう。
console.log(obj1 instanceof Array); // -> false
console.log(obj2 instanceof Array); // -> false
console.log(obj3 instanceof Array); // -> true
console.log(obj4 instanceof Array); // -> true
後半は配列なので true
ですが、前半は配列ではありませんので false
です。予想通りで良かったですね。
更に __proto__
と prototype
による確認
クラスの prototype
はどうなっているのか。
それは作成した各オブジェクトの __proto__
属性からポイントされています。
console.log(obj1.__proto__ === Object.prototype); // -> true
console.log(obj2.__proto__ === Object.prototype); // -> true
console.log(obj3.__proto__ === Array.prototype); // -> true
console.log(obj4.__proto__ === Array.prototype); // -> true
// getProto(obj)
var getProto = Object.getPrototypeOf ? Object.getPrototypeOf :
function getProto(obj) { return obj.__proto__; };
console.log(getProto(obj1) === Object.prototype); // -> true
console.log(getProto(obj2) === Object.prototype); // -> true
console.log(getProto(obj3) === Array.prototype); // -> true
console.log(getProto(obj4) === Array.prototype); // -> true
それぞれのクラスのプロトタイプ・オブジェクトをポイントしているので問題ないですね。
__proto__
の __proto__
による確認
ついでに __proto__
の __proto__
も確認しておきましょう。
console.log(obj1.__proto__.__proto__ === null); // -> true
console.log(obj2.__proto__.__proto__ === null); // -> true
console.log(obj3.__proto__.__proto__ === Object.prototype); // -> true
console.log(obj4.__proto__.__proto__ === Object.prototype); // -> true
console.log(obj3.__proto__.__proto__.__proto__ === null); // -> true
console.log(obj4.__proto__.__proto__.__proto__ === null); // -> true
console.log(getProto(getProto(obj1)) === null); // -> true
console.log(getProto(getProto(obj2)) === null); // -> true
console.log(getProto(getProto(obj3)) === Object.prototype); // -> true
console.log(getProto(getProto(obj4)) === Object.prototype); // -> true
console.log(getProto(getProto(getProto(obj3))) === null); // -> true
console.log(getProto(getProto(getProto(obj4))) === null); // -> true
Object
の親クラスは null
に行き着いておしまい。
Array
の親クラスは Object
で、更に親はいない、という事ですね。
この先祖をたぐれる様になっている仕組みがプロトタイプ・チェーンです。
ちなみに __proto__
は非標準です。
標準では Object.getPrototypeOf
を使う様に、との事。
今度は constructor.name
による確認
constructor
はオブジェクトを作成する時に使用されたコンストラクタ関数を指しており、関数に名前がある場合 name
属性を見れば、その関数名がわかります。
console.log(obj1.constructor.name); // -> Object
console.log(obj2.constructor.name); // -> Object
console.log(obj3.constructor.name); // -> Array
console.log(obj4.constructor.name); // -> Array
正しく表示できましたか。もし思った通りに表示されない場合、以下の互換性対応のおまじないコードを先に実行して、もう一度上記のコードを実行してくれますか。うまくいく事を祈ります。
// 互換性対応のおまじない。主に IE。
var fnameRegExp = /^\s*function\s*\**\s*([^\(\s]*)[\S\s]+$/im;
// fname: get function name
function fname() {
return ('' + this).replace(fnameRegExp, '$1');
}
// Function.prototype.name
if (!Function.prototype.hasOwnProperty('name')) {
if (Object.defineProperty)
Object.defineProperty(Function.prototype, 'name', {get: fname});
else if (Object.prototype.__defineGetter__)
Function.prototype.__defineGetter__('name', fname);
}
関数の name
属性は非標準なので本来は使用してはいけないのですが、デバッグする時に非常に役に立つので上記のコードを実行してから使う事にします。あしからず。
データだけでなくメソッドも定義してみる
幅(w)と高さ(h)で四角形の面積を計算してみる。
var x1 = {w: 10, h: 20, calc: function () { return this.w * this.h; }};
var x2 = {w: 20, h: 30, calc: function () { return this.w * this.h; }};
console.log(x1.calc()); // -> 200
console.log(x2.calc()); // -> 600
console.log(x1.calc === x2.calc); // -> false
console.log(x1.calc.toString() === x2.calc.toString()); // -> true
メソッドとしての関数を記憶するためのフィールドが、オブジェクトのインスタンスそれぞれに必要となりますね。同じ様に関数定義しても違う関数が登録されている様です。またメソッドの数だけ記憶領域が必要です。あまり効率が良いとは言えませんね。
ちょっとだけマシな定義にするには以下の様にすればいいのでしょうか。
function calc() {
return this.w * this.h;
}
var x3 = {w: 10, h: 20, calc: calc};
var x4 = {w: 20, h: 30, calc: calc};
console.log(x3.calc()); // -> 200
console.log(x4.calc()); // -> 600
console.log(x3.calc === x4.calc); // -> true
面積をプロパティの様に扱う事も可能です。
その様な場合 getter を使います。
var x5 = {w: 10, h: 20, get area() { return this.w * this.h; }};
var x6 = {w: 20, h: 30, get area() { return this.w * this.h; }};
console.log(x5.area); // -> 200
console.log(x6.area); // -> 600
ここには例はあげませんでしたが setter も勉強しておきましょう。
こちらに示したサンプルは非常に簡単ですが、本格的にオブジェクトをたくさん作成するには、記憶領域の効率はあまり良くないと思います。
直接オブジェクトを作成するパターンはこれくらいにして本題に進みましょう。
プロトタイプベースのオブジェクト指向
みなさんはクラスベースのオブジェクト指向言語として Java, C#, C++ なども勉強されているのではないかと思いますが、JavaScript はプロトタイプベースのオブジェクト指向言語なので、ちょっと変わった振る舞いをします。様々なクラス定義の例をみながら検証していきましょう。
シンプルなクラス定義
JavaScript ではコンストラクタ関数を定義する事で、クラス定義の様に、記述する事が可能です。
まずは Animal
クラスを定義してみましょう。
// Animal クラス定義
function Animal(name) {
this.name = name;
}
// Animal クラスのメソッド定義
Animal.prototype.introduce = function introduce() {
console.log('私は ' + this.constructor.name + ' の ' + this.name + ' です。');
};
インスタンス作成と利用
では Animal
クラスのインスタンスオブジェクトを作成し、利用してみましょう。
// Animal クラスのインスタンスオブジェクトの作成と利用
var a1 = new Animal('Annie');
a1.introduce(); // -> 私は Animal の Annie です。
this.constructor.name
が使用できるように、上記の様に関数定義としてクラス名を関数に付ける方が良いと思います。
インスタンスオブジェクトの検証
では、このクラスのインスタンスがちゃんとできているか検証してみましょう。
まずはクラス定義とオブジェクトが正しく作成できているか検証するための関数を作りましょう。
var CSI = '\u001b['; // ANSI Control Sequence Introducer
var NORMAL = typeof window !== 'undefined' ? '' : CSI + 'm';
var GREEN = typeof window !== 'undefined' ? '' : CSI + '32m';
var RED = typeof window !== 'undefined' ? '' : CSI + '31m';
var YELLOW = typeof window !== 'undefined' ? '' : CSI + '33m';
// getProto(obj)
var getProto = Object.getPrototypeOf ? Object.getPrototypeOf :
function getProto(obj) { return obj.__proto__; };
// assertTrue: true であればOK、false の時はエラーメッセージを表示する
function assertTrue(bool, msg) {
if (!bool) {
console.error(RED + 'Error: ' + msg + NORMAL);
}
}
// verifyClassObject: オブジェクトの検証
function verifyClassObject(obj, expected, keysExpected) {
var name = expected[0];
var TheClass = expected[1];
var SuperClass = expected[2];
var keys = [];
for (var i in obj) {
keys.push(i);
}
var keysActual = keys.join(',');
if (keysActual === keysExpected) {
console.info(GREEN + 'Success: keys = ' + keysActual + NORMAL);
}
else {
console.error(RED + 'Error: keys = ' + keysActual + ', ' + NORMAL +
YELLOW + 'Expected: keys ' + keysExpected + NORMAL);
}
// obj は Class のインスタンスだ (new Class で作成したからね)
assertTrue(obj instanceof TheClass,
name + ' は ' + TheClass.name + ' のインスタンスではない。');
// obj は SuperClass のインスタンスでもある
if (SuperClass) {
assertTrue(obj instanceof SuperClass,
name + ' は ' + SuperClass.name + ' のインスタンスではない。');
}
// obj は Object のインスタンスでもある
assertTrue(obj instanceof Object,
name + ' は ' + Object.name + ' のインスタンスではない。');
// obj のコンストラクタは Class だ
assertTrue(obj.constructor === TheClass,
name + ' のコンストラクタは ' + obj.constructor.name + ' で、 ' +
TheClass.name + ' ではない。');
// Class のプロトタイプオブジェクトのコンストラクタは Class だ
assertTrue(TheClass.prototype.constructor === TheClass,
TheClass.name + ' のプロトタイプは ' + TheClass.prototype.constructor.name + ' で、 ' +
TheClass.name + ' ではない。');
// obj の __proto__ を見てみると...
assertTrue(getProto(obj).constructor === TheClass,
name + ' の __proto__ は ' + getProto(obj).constructor.name + ' で、 ' +
TheClass.name + ' ではない。');
// Class は SuperClass を継承しているんだね
if (SuperClass) {
assertTrue(getProto(getProto(obj)) === SuperClass.prototype &&
getProto(getProto(obj)).constructor === SuperClass &&
getProto(TheClass.prototype).constructor === SuperClass,
name + ' の __proto__ の __proto__ は ' +
getProto(getProto(obj)).constructor.name + ' で、 ' +
SuperClass.name + ' ではない。');
}
// obj の先祖を辿ってみる...
var expectedString = expected.map(function (fn) {
return typeof fn === 'function' ? fn.name : fn;
}).join(' < ');
var ancestors = [name];
for (var obj = getProto(obj); obj; obj = getProto(obj)) {
ancestors.push(obj.constructor.name);
}
var actualString = ancestors.join(' < ');
if (actualString === expectedString) {
console.info(GREEN + 'Success: ' + actualString + NORMAL);
}
else {
console.error(RED + 'Error: ' + actualString + ', ' + NORMAL +
YELLOW + 'Expected: ' + expectedString + NORMAL);
}
// -> name < Class < SuperClass < Object
}
if (!('info' in console)) { console.info = console.log; }
if (!('error' in console)) { console.error = console.log; }
a1 < Animal < Object を検証する
では作成したオブジェクトを検証してみましょう。
// a1 < Animal < Object かどうか検証してみる
verifyClassObject(a1, ['a1', Animal, Object], 'name,introduce');
// -> Success: keys = name,introduce
// -> Success: a1 < Animal < Object
もちろん、シンプルな例ですので、特に問題はありませんね。
関数定義には名前を付ける
a1.constructor.name
で検証ができる様に関数定義には名前を付ける方が良いと考えています。エラー発生時のトレースバック情報にも関数名が含まれますので、そういう場合にも詳しい情報が表示されるので有効です。それはメソッドの関数名も同様です。
もちろん、以下の様な形式でクラス定義してもいいですが、名前が無いクラスを作っているようなものなので、私は推奨はしません。
var Animal = function (name) {
this.name = name;
};
せめて以下の様に名前を付けましょう。
var Animal = function Animal(name) {
this.name = name;
};
さきほどの簡単なオブジェクトもついでに検証
さきほど作成した簡単なオブジェクト obj1
~obj2
, x1
~x6
も検証してみましょう。
verifyClassObject(obj1, ['obj1', Object], 'x,y');
// -> Success: keys = x,y
// -> Success: obj1 < Object
verifyClassObject(obj2, ['obj2', Object], 'x,y');
// -> Success: keys = x,y
// -> Success: obj2 < Object
verifyClassObject(obj3, ['obj3', Array, Object], '0,1');
// -> Success: keys = 0,1
// -> Success: obj3 < Array < Object
verifyClassObject(obj4, ['obj4', Array, Object], '0,1');
// -> Success: keys = 0,1
// -> Success: obj4 < Array < Object
verifyClassObject(x1, ['x1', Object], 'w,h,calc');
// -> Success: keys = w,h,calc
// -> Success: x1 < Object
verifyClassObject(x2, ['x2', Object], 'w,h,calc');
// -> Success: keys = w,h,calc
// -> Success: x2 < Object
verifyClassObject(x3, ['x3', Object], 'w,h,calc');
// -> Success: keys = w,h,calc
// -> Success: x3 < Object
verifyClassObject(x4, ['x4', Object], 'w,h,calc');
// -> Success: keys = w,h,calc
// -> Success: x4 < Object
verifyClassObject(x5, ['x5', Object], 'w,h,area');
// -> Success: keys = w,h,area
// -> Success: x5 < Object
verifyClassObject(x6, ['x6', Object], 'w,h,area');
// -> Success: keys = w,h,area
// -> Success: x6 < Object
当然ですが、全て問題ありません。そうなる様に引数をセットしました。
やっちゃいけない継承、その1
それでは Animal
クラスを継承して Bear
クラスを定義してみましょう。
// Bear クラス定義
function Bear(name) {
Animal.call(this, name);
}
// やっちゃいけない継承、その1
Bear.prototype = Animal.prototype;
// Bear クラスのインスタンスオブジェクトの作成と利用
var b1 = new Bear('Pooh');
b1.introduce(); // -> 私は Animal の Pooh です。
// b1 < Bear < Animal < Object かどうか検証してみる
verifyClassObject(b1, ['b1', Bear, Animal, Object], 'name,introduce');
// -> Success: keys = name,introduce
// -> Error: b1 のコンストラクタは Animal で、 Bear ではない。
// -> Error: Bear のプロトタイプは Animal で、 Bear ではない。
// -> Error: b1 の __proto__ は Animal で、 Bear ではない。
// -> Error: b1 の __proto__ の __proto__ は Object で、 Animal ではない。
// -> Error: b1 < Animal < Object, Expected: b1 < Bear < Animal < Object
なんとなく動いているように見えるんですけど、検証結果はどうでしょうか。
いくつか問題がありそうです。
b1 < Bear < Animal < Object
っていうのがいいと思うけど、なんか違うね。
では、以下の様な Bear
クラスのメソッド定義をするとどうなるでしょうか。
Bear.prototype.bearMethod = function bearMethod() {};
そうです。Animal
クラスにも同じ定義ができてしまいますね。
これでは、おかしなことになりそうですね。
もし上記を実行してしまったなら以下で削除しておきましょう。
delete Bear.prototype.bearMethod;
やっちゃいけない継承、その2
今度は Animal
クラスを継承して Cat
クラスを定義してみましょう。
// Cat クラス定義
function Cat(name) {
Animal.call(this, name);
}
// やっちゃいけない継承、その2
Cat.prototype = new Animal;
// Cat クラスのインスタンスオブジェクトの作成と利用
var c1 = new Cat('Kitty');
c1.introduce(); // -> 私は Animal の Kitty です。
// c1 < Cat < Animal < Object かどうか検証してみる
verifyClassObject(c1, ['c1', Cat, Animal, Object], 'name,introduce');
// -> Success: keys = name,introduce
// -> Error: c1 のコンストラクタは Animal で、 Cat ではない。
// -> Error: Cat のプロトタイプは Animal で、 Cat ではない。
// -> Error: c1 の __proto__ は Animal で、 Cat ではない。
// -> Error: c1 < Animal < Animal < Object, Expected: c1 < Cat < Animal < Object
なんとなく動いているように見えるけど、検証結果はどうでしょうか。
やはり、いくつか問題がありそうです。
c1 < Cat < Animal < Object
っていうのがいいと思うけど、やっぱりなんか違うね。
結果は正しそうだけど互換性が無い継承
今度は constructor
と __proto__
を使って Animal
クラスを継承して Dog
クラスを定義してみましょう。
// Dog クラス定義
function Dog(name) {
this.name = name;
}
// 無理やり constructor と __proto__ を使って prototype オブジェクトを上書きする
Dog.prototype = {
constructor: Dog,
__proto__: Animal.prototype
};
// Dog クラスのインスタンスオブジェクトの作成と利用
var d1 = new Dog('Hachi');
d1.introduce(); // -> 私は Dog の Hachi です。
// d1 < Dog < Animal < Object かどうか検証してみる
verifyClassObject(d1, ['d1', Dog, Animal, Object], 'name,introduce');
// -> Error: keys = name,constructor,introduce, Expected: keys name,introduce
// -> Success: d1 < Dog < Animal < Object
検証結果は正しそうでしたが for in
で余計な constructor
が出てきました。
また __proto__
を使うのは非標準です。
ところで ClassName.prototype = {}
って、やっていいの?
なんか、これが問題になっているような気がしますね。
こういう ClassName.prototype = {}
という記述を見たら間違いと思った方がいいでしょうね。
結果は正しいけど互換性が無い継承
今度は __proto__
だけを使用して Animal
クラスを継承して Elephant
クラスを定義してみましょう。
// Elephant クラス定義
function Elephant(name) {
Animal.call(this, name);
}
// 結果は正しいけど互換性が無い継承
Elephant.prototype.__proto__ = Animal.prototype;
// Elephant クラスのインスタンスオブジェクトの作成と利用
var e1 = new Elephant('Dumbo');
e1.introduce(); // -> 私は Elephant の Dumbo です。
// e1 < Elephant < Animal < Object かどうか検証してみる
verifyClassObject(e1, ['e1', Elephant, Animal, Object], 'name,introduce');
// -> Success: keys = name,introduce
// -> Success: e1 < Elephant < Animal < Object
検証結果は正しそうですね。
ですが __proto__
は標準ではありません。
互換性のないキーワードを使っている事が問題です。
正しそうな継承
今度は Object.create
と constructor
を使って Animal
クラスを継承して Fox
クラスを定義してみましょう。
// Fox クラス定義
function Fox(name) {
Animal.call(this, name);
}
Fox.prototype = Object.create(Animal.prototype);
Fox.prototype.constructor = Fox;
// Fox クラスのインスタンスオブジェクトの作成と利用
var f1 = new Fox('Gon');
f1.introduce(); // -> 私は Fox の Gon です。
// f1 < Fox < Animal < Object かどうか検証してみる
verifyClassObject(f1, ['f1', Fox, Animal, Object], 'name,introduce');
// -> Error: keys = name,constructor,introduce, Expected: keys name,introduce
// -> Success: f1 < Fox < Animal < Object
検証結果は正しそうでしたが for in
で余計な constructor
が出てきました。
やはり enumerable: false
でないとまずいですね。
正しい継承
今度は Animal
クラスを継承して Gorilla
クラスを定義してみましょう。
継承させるための関数 inherits
として Node.js の util.inherits
をそのまま使用してみましょう。
// Gorilla クラス定義
function Gorilla(name) {
Animal.call(this, name);
}
// console.log(require('util').inherits.toString()); より
function inherits(ctor, superCtor) {
if (ctor === undefined || ctor === null)
throw new TypeError('The constructor to `inherits` must not be ' +
'null or undefined.');
if (superCtor === undefined || superCtor === null)
throw new TypeError('The super constructor to `inherits` must not ' +
'be null or undefined.');
if (superCtor.prototype === undefined)
throw new TypeError('The super constructor to `inherits` must ' +
'have a prototype.');
ctor.super_ = superCtor;
Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
// ctor.prototype = Object.create(superCtor.prototype, {
// constructor: {
// value: ctor,
// enumerable: false,
// writable: true,
// configurable: true
// }
// });
}
// 正しい継承
inherits(Gorilla, Animal);
// Gorilla クラスのインスタンスオブジェクトの作成と利用
var g1 = new Gorilla('Kong');
g1.introduce();
// -> 私は Gorilla の Kong です。
// g1 < Gorilla < Animal < Object かどうか検証してみる
verifyClassObject(g1, ['g1', Gorilla, Animal, Object], 'name,introduce');
// -> Success: keys = name,introduce
// -> Success: g1 < Gorilla < Animal < Object
検証結果も、もちろん定義も問題ありませんね。
おまけ1: new
を付け忘れた時の対応
以下の様に new
を付け忘れた場合どうなるでしょうか。
try {
var a2 = Animal('Annie');
} catch (err) {
console.log(RED + err + NORMAL);
}
this
オブジェクトがグローバルオブジェクトになってしまいますので、以下の様にガードする様にしましょう。
// Animal2 クラス定義
function Animal2(name) {
if (!(this instanceof Animal2)) {
return new Animal2(name);
}
this.name = name;
}
var a2 = Animal2('Annie');
おまけ2: Closure を使って定義する
以下のコードを見てください。
var a3 = new Animal('Annie');
a3.introduce(); // -> 私は Animal の Annie です。
console.log(a3.name); // -> Annie
a3.name = 'Aho';
a3.introduce(); // -> 私は Animal の Aho です。
console.log(a3.name); // -> Aho
name
属性が外から書き換えられてしまいますね。
Java, C#, C++ 等の言語でいう所の private なフィールドは無いのでしょうか。
Closure を使うとそれに似た事ができます。
// Animal3 クラス定義
function Animal3(name) {
this.introduce = function introduce() {
console.log('私は ' + this.constructor.name + ' の ' + name + ' です。');
};
}
var a3 = new Animal3('Annie');
a3.introduce(); // -> 私は Animal3 の Annie です。
console.log(a3.name); // -> undefined
a3.name = 'Aho';
a3.introduce(); // -> 私は Animal3 の Annie です。
console.log(a3.name); // -> Aho
それぞれのオブジェクトにメソッド数だけ関数定義が必要になりますが、外部から内部の変数へのアクセスは絶対に不可能になります。公開されたメソッドでないと内部の変数にアクセスすることができないので非常に安全です。
それぞれのオブジェクトに属性を定義していくのか、コンストラクタ関数内の変数を定義し、公開メソッドを定義していくのか、全く違った設計になると思います。
記憶領域の効率は良くないですし、若干遅いのであまりお勧めはしません。
おまけ3: クラス共通の属性定義
以下の様に prototype
に共通の属性を定義すると、デフォルト値の様なものが定義できます。それを上書きすることもできます。
// Animal4 クラス定義
function Animal4(name) {
this.name = name;
this.animalProp = 123;
}
Animal4.prototype.animalCommonProp = 'abc';
var a4 = new Animal4('Annie');
console.log(a4.animalProp + ' ' + a4.animalCommonProp);
// -> 123 abc
a4.animalProp = 456;
a4.animalCommonProp = 'xyz';
console.log(a4.animalProp + ' ' + a4.animalCommonProp);
// -> 456 xyz
delete a4.animalProp;
delete a4.animalCommonProp;
console.log(a4.animalProp + ' ' + a4.animalCommonProp);
// -> undefined abc
最後の行、注意してくださいね。
参考文献
Node.js のリリースノートに正しい継承方法が記述されていました
[v0.8 から v0.10 の主なAPI変更 - Node.js]
(https://github.com/nodejsjp/nodejs.org_ja/wiki/API-changes-between-v0.8-and-v0.10)
EventEmitter
を継承させる時の注意点として記述があります。
型判定
[JavaScriptの「型」の判定について - Qiita]
(http://qiita.com/south37/items/c8d20a069fcbfe4fce85)
instanceof
を正しく使える様に定義していくべきでしょうね。
型判定には typeof
が高速なので、まずはそれを使うべきかなぁと思います。
重要な事が記述されています
[Javascriptでオブジェクト指向するときに覚えておくべきこと - Qiita]
(http://qiita.com/awakia/items/8ff451ca5f8ae0122be7)
以下のような記事を読んで奮起しました。
[【メモ】JavaScriptで継承(っぽいの)- Qiita]
(http://qiita.com/ryokio0129/items/ddf354639889bf4a03e6)
っぽいんですけど、やはり...
[javascriptでの継承の基本パターン4つ - Qiita]
(http://qiita.com/norami_dream/items/ea3827f05699afcb1cc5)
検証してみていない...