JavaScriptでクラスというか疑似クラスというか、いわゆる継承を行おうとしてサンプルコード探すと、世の中に様々なコードがあふれていて、何がなにやらわからなくて、どれが正解かわかりませんでしたので混乱します。
頭いたいわ....
他言語だと、あるやり方はこれが正解、というのは確実にすぐにわかるのですが、JavaScriptは環境やバージョンなどによって、あるやり方の正解を見つけるのが、非常に困難ですね。地獄やで。
その中でも、より、どれが正解かを見つけないといけないでしょう。
ですので、動作確認してみました。
世間にある、クラス継承コードのほぼ全パターンを網羅して比較しています。
(ES6のクラス構文は比較してません。ES6を使うならクラス構文を使った方がいいんじゃないでしょうか。Babelで変換したら、きっとこのどれかのパターンに変換されると思います)
書き方の問題で異なったコードに見えるかもしれませんが、やっていることは同じことです。
思った以上に、ひどく手間だったので、文章かくのはもうめんどくさくなりました。動作確認コードを全部のせます。誰の環境でもすぐに動作確認できると思います。
コードのコメントにもいろいろ書いたので読める人は読めると思います。
自分で動かして、自分で試して、自分で選択してみてください。
環境はWindows、chrome、node.js、wsh-jscriptです
各バージョンは....調べる方法があまりわからないので、いいです。
それぞれの現在の日付で、ほどほどに最新版です。
(このあたりの仕様はもう今後は変わらないでしょうから、大丈夫でしょう。)
ブラウザで、読み込むか、
run_node.bat をダブルクリックで起動するか(node.jsはインストールしといて)
run_wsh.wsh をダブルクリックで起動するか、で、動作確認できます。
動かすと、test finish というメッセージが出るだけで、他にメッセージは出ないです。
メッセージが出てしまうと、正しく動作していないので、どこかを修正する必要があります。
(掲載コードはテスト終了メッセージしか出ないように調整しています)
結論から言うと、2系列と7系列と8系列の実装が、テストを通過して正しくクラス的なものを実装しています。他のものは機能が足りないので、落選です。
7系列8系列はコード量がちょっと多くなるので、2系列で十分かと思いましたが、8系列のほうがいいかも、と思ったので、そちらを採用しています。
細かくいうと、2系列では、インスタンス作成タイミングが親クラスと子クラスとで異なってしまう、という問題がありそうです。ほとんど気にならないようなこととは思います。
7系列は、自分は所有していませんが「サイ本」に載っているやり方だそうです。8系列は、Google Closure Library と同じだそうです。
また、コンストラクタ引数については考慮してません。
これ以上テストを複雑にしたくもないのと、コンストラクタ引数なくても、実質こまらないでしょ、と思いました。
考慮したい人は、試してみてください。
他のクラス実装方法はあるかもしれませんが、現在、webで調べまくった結果、これらに集約されていると思いますが、まだまだ作り方があるかもしれません。
ご自身で9系列とか、10系列とか作って動かすのもいいんじゃないでしょうか。
0系列(仮想的な擬似コード)のコメントで示したテストを通過できるかどうか、ということで動作確認して試すとよいでしょう。
テストコードそのものに漏れがある場合もあります。
JavaScriptの仕様に詳しくないので、2系列と7系列と8系列の差異をみつけることもできませんでした。
クラスとして、何が望ましいのか、ということ自体が人によって様々な定義があると思うので
必要ならテストを追加して動かしてみてください。
参考サイト
・プログラマのためのJavaScript (11):継承についてもう少し - 檜山正幸のキマイラ飼育記
http://d.hatena.ne.jp/m-hiyama/20051017/1129510043
・JavaScriptの継承について
https://gist.github.com/y-yu/3301790
・JavaScriptにおける継承のパターン4種類の概要と対比 | プログラミング | POSTD
http://postd.cc/javascript-inheritance-patterns/
・Google流 JavaScript におけるクラス定義の実現方法
http://www.yunabe.jp/docs/javascript_class_in_google.html
・JavaScriptでクラスの継承的なことする - おっさんプログラマの戯れ言
http://d.hatena.ne.jp/unk_pizza/20161208/p1
・[JavaScript] そんな継承はイヤだ - クラス定義 - オブジェクト作成 - Qiita
http://qiita.com/LightSpeedC/items/d307d809ecf2710bd957
・JavaScriptで(そしてどんな言語でも同じで)世界一簡単なテストフレームワークを作って使おう - Qiita
http://qiita.com/standard-software/items/559d871794bfa38651f4
ソースコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<script src="script.js"></script>
</head>
<body>
<script>
window.addEventListener("load",function(eve){
main('browser');
},false);
</script>
</body>
</html>
var main = require('./script.js');
main('browser');
node run_node.js
pause
<?xml version="1.0" encoding="shift-jis" ?>
<job>
<script language="JavaScript" src=".\script.js"></script>
<script language="JavaScript">
<![CDATA[
var alert = function (message) {
WScript.Echo(message);
}
main('WSH-wsf');
]]>
</script>
</job>
'use strict';
var alert = alert || function (message) {
console.log(message);
};
var main = function (testMode) {
var check = function(a, b) {
var message;
if (a !== b) {
message =
'A != B' + '\n' +
'A = ' + a + '\n' +
'B = ' + b;
alert(message);
}
};
/*
◇前提
・慣例として、大文字で始まる関数は
コンストラクタとして使われる
つまり、
var animal = new Animal();
のように使われる。
animalはインスタンスで
Animalはクラス、というか、
インスタンスを作成するコンストラクタ関数となる。
・check関数は、2値を比較して
一致ならメッセージを出す
不一致なら何もしない。
下記のコードは、check関数で一切メッセージがでない
(そのように作っている)
参考
・JavaScriptで(そしてどんな言語でも同じで)世界一簡単なテストフレームワークを作って使おう - Qiita
http://qiita.com/standard-software/items/559d871794bfa38651f4
・wsh-jscriptは、constructor.name は未定義なので
constructorで一致を調べなければいけない
*/
//----------------------------------------
//このコードを0系列と呼ぶことにします。
//ここは実際には動かさないのでコメントアウトしておきます。
/*
//親クラス定義
function Animal0() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog0() {
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
//孫クラス定義
function ShibaInu0() {
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
//countryは出身地ね
}
//--------------------
//テストコード
//犬
var dog0 = new Dog0();
check('ワンワン', dog0.hello);
//犬のhelloはワンワン
check(0, dog0.hand);
check(4, dog0.leg);
//犬は動物のプロパティを持つ
check('尻尾ある', dog0.tail);
//犬インスタンスは犬クラスのプロパティを持つ
check(true, typeof dog0.country === 'undefined');
//犬には出身地のプロパティはありません。
check(true, dog0 instanceof Dog0);
//犬はDog0コンストラクタから作られています
//instanceofを使って確認
check(true, Dog0 === dog0.constructor);
//犬はDog0コンストラクタから作られています
//constructor を使って確認
//柴犬
var shibaInu0 = new ShibaInu0();
check('柴犬だワン!', shibaInu0.hello);
//柴犬のhello
check(0, shibaInu0.hand);
check(4, shibaInu0.leg);
//柴犬は動物のプロパティを持つ
check('尻尾ある', shibaInu0.tail);
//柴犬インスタンスは犬クラスのプロパティを持つ
check(false, typeof shibaInu0.country === 'undefined');
//柴犬は出身地情報があります。
check('柴県出身です。', shibaInu0.country);
check(true, shibaInu0 instanceof Dog0);
//柴犬はDog0コンストラクタの情報を含んでいます
//instanceofを使って確認
check(true, shibaInu0 instanceof ShibaInu0);
//柴犬はshibaInu0コンストラクタの情報も含んでいます
//instanceofを使って確認
check(true, ShibaInu0 === shibaInu0.constructor);
//犬はShibaInu0コンストラクタから作られています
//constructor を使って確認
*/
//----------------------------------------
//このコードを1系列と呼ぶことにします。
// Dog1.prototype = new Animal1();
//この部分によって継承っぽい事を実現しています。
//結論としては
//一見、正しく動いているように見えるが
//constructorのところが正しく動いてくれていません。
//環境によらず誤動作しています。
//子クラスが、何を継承しているのか、
//は、instanceofで見分けることができますが
//実際にはどのクラスのインスタンスなのか
//を、constructorで見分けることができません。
var test1 = function (testMode) {
//親クラス定義
function Animal1() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog1() {
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
Dog1.prototype = new Animal1();
//孫クラス定義
function ShibaInu1() {
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
ShibaInu1.prototype = new Dog1();
//--------------------
//テストコード
//犬
var dog1 = new Dog1();
check('ワンワン', dog1.hello);
check(0, dog1.hand);
check(4, dog1.leg);
check('尻尾ある', dog1.tail);
check(true, typeof dog1.country === 'undefined');
check(true, dog1 instanceof Dog1);
//これが理想形
//check(true, Dog1 === dog1.constructor);
check(true, Animal1 === dog1.constructor);
//柴犬
var shibaInu1 = new ShibaInu1();
check('柴犬だワン!', shibaInu1.hello);
check(0, shibaInu1.hand);
check(4, shibaInu1.leg);
check('尻尾ある', shibaInu1.tail);
check(false, typeof shibaInu1.country === 'undefined');
check('柴県出身です。', shibaInu1.country);
check(true, shibaInu1 instanceof Dog1);
check(true, shibaInu1 instanceof ShibaInu1);
//これが理想形
//check(true, ShibaInu1 === shibaInu1.constructor);
check(true, Animal1 === shibaInu1.constructor);
}
//----------------------------------------
//このコードを2系列と呼ぶことにします。
//1系列に加えて、constructorを無理やり指定しています
// Dog2.prototype = new Animal2();
// Dog2.prototype.constructor = Animal2;
//この部分によって継承っぽい事を実現しています。
//結論としては、
//環境によらずに正しく動作しています。
//参考
// プログラマのためのJavaScript (11):継承についてもう少し - 檜山正幸のキマイラ飼育記
// http://d.hatena.ne.jp/m-hiyama/20051017/1129510043
var test2 = function (testMode) {
//親クラス定義
function Animal2() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog2() {
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
Dog2.prototype = new Animal2();
Dog2.prototype.constructor = Dog2;
//孫クラス定義
function ShibaInu2() {
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
ShibaInu2.prototype = new Dog2();
ShibaInu2.prototype.constructor = ShibaInu2;
//--------------------
//テストコード
//犬
var dog2 = new Dog2();
check('ワンワン', dog2.hello);
check(0, dog2.hand);
check(4, dog2.leg);
check('尻尾ある', dog2.tail);
check(true, typeof dog2.country === 'undefined');
check(true, dog2 instanceof Dog2);
check(true, Dog2 === dog2.constructor);
//柴犬
var shibaInu2 = new ShibaInu2();
check('柴犬だワン!', shibaInu2.hello);
check(0, shibaInu2.hand);
check(4, shibaInu2.leg);
check('尻尾ある', shibaInu2.tail);
check(false, typeof shibaInu2.country === 'undefined');
check('柴県出身です。', shibaInu2.country);
check(true, shibaInu2 instanceof Dog2);
check(true, shibaInu2 instanceof ShibaInu2);
check(true, ShibaInu2 === shibaInu2.constructor);
}
//----------------------------------------
//このコードを3系列と呼ぶことにします。
// Animal3.call(this);
//この部分によって継承っぽい事を実現しています。
//結論としては、
//環境によらず
// shibaInu3 instanceof Dog3
//ここが誤動作するので、
//子クラスが、何を継承しているのか、
//は、instanceofで見分けることができますが
//これが正しく動かないため、実用的ではありません。
var test3 = function (testMode) {
//親クラス定義
function Animal3() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog3() {
Animal3.call(this);
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
//孫クラス定義
function ShibaInu3() {
Dog3.call(this);
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
//--------------------
//テストコード
//犬
var dog3 = new Dog3();
check('ワンワン', dog3.hello);
check(0, dog3.hand);
check(4, dog3.leg);
check('尻尾ある', dog3.tail);
check(true, typeof dog3.country === 'undefined');
check(true, dog3 instanceof Dog3);
check(true, Dog3 === dog3.constructor);
//柴犬
var shibaInu3 = new ShibaInu3();
check('柴犬だワン!', shibaInu3.hello);
check(0, shibaInu3.hand);
check(4, shibaInu3.leg);
check('尻尾ある', shibaInu3.tail);
check(false, typeof shibaInu3.country === 'undefined');
check('柴県出身です。', shibaInu3.country);
//これが理想形
//check(true, shibaInu3 instanceof Dog3);
check(false, shibaInu3 instanceof Dog3);
check(true, shibaInu3 instanceof ShibaInu3);
check(true, ShibaInu3 === shibaInu3.constructor);
}
//----------------------------------------
//このコードを4系列と呼ぶことにします。
// Animal4.call(this);
// Object.setPrototypeOf(Dog4.prototype, Animal4.prototype);
//この部分によって継承っぽい事を実現しています。
//このコードはWSHではsetPrototypeOfに対応していないので
//WSHではテストしません。
//結論としては、
//browser環境では正しく動作しました。
//WSHの場合は動作しません。
//参考
// JavaScriptにおける継承のパターン4種類の概要と対比 | プログラミング | POSTD
// http://postd.cc/javascript-inheritance-patterns/
var test4 = function (testMode) {
//Object.setPrototypeOfの関数定義だけで
//エラーにならないように対策
Object.setPrototypeOf = Object.setPrototypeOf ||
function () {};
//親クラス定義
function Animal4() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog4() {
Animal4.call(this);
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
Object.setPrototypeOf(
Dog4.prototype, Animal4.prototype);
//孫クラス定義
function ShibaInu4() {
Dog4.call(this);
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
Object.setPrototypeOf(
ShibaInu4.prototype, Dog4.prototype);
//--------------------
//テストコード
//犬
var dog4 = new Dog4();
check('ワンワン', dog4.hello);
check(0, dog4.hand);
check(4, dog4.leg);
check('尻尾ある', dog4.tail);
check(true, typeof dog4.country === 'undefined');
check(true, dog4 instanceof Dog4);
check(true, Dog4 === dog4.constructor);
//柴犬
var shibaInu4 = new ShibaInu4();
check('柴犬だワン!', shibaInu4.hello);
check(0, shibaInu4.hand);
check(4, shibaInu4.leg);
check('尻尾ある', shibaInu4.tail);
check(false, typeof shibaInu4.country === 'undefined');
check('柴県出身です。', shibaInu4.country);
check(true, shibaInu4 instanceof Dog4);
check(true, shibaInu4 instanceof ShibaInu4);
check(true, ShibaInu4 === shibaInu4.constructor);
}
//----------------------------------------
//このコードを5系列と呼ぶことにします。
// Animal5.call(this);
// Dog5.prototype.__proto__ = Animal5.prototype;
//この部分によって継承っぽい事を実現しています。
//結論としては、
//browser環境では正しく動作しました。
//WSHの場合は__proto__には対応していないので
//その部分がなかったかのような3系列と同じ動きをします
var test5 = function (testMode) {
//親クラス定義
function Animal5() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog5() {
Animal5.call(this);
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
Dog5.prototype.__proto__ = Animal5.prototype;
//孫クラス定義
function ShibaInu5() {
Dog5.call(this);
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
ShibaInu5.prototype.__proto__ = Dog5.prototype;
//--------------------
//テストコード
//犬
var dog5 = new Dog5();
check('ワンワン', dog5.hello);
check(0, dog5.hand);
check(4, dog5.leg);
check('尻尾ある', dog5.tail);
check(true, typeof dog5.country === 'undefined');
check(true, dog5 instanceof Dog5);
check(true, Dog5 === dog5.constructor);
//柴犬
var shibaInu5 = new ShibaInu5();
check('柴犬だワン!', shibaInu5.hello);
check(0, shibaInu5.hand);
check(4, shibaInu5.leg);
check('尻尾ある', shibaInu5.tail);
check(false, typeof shibaInu5.country === 'undefined');
check('柴県出身です。', shibaInu5.country);
//これが理想形
//check(true, shibaInu5 instanceof Dog5);
switch (testMode) {
case 'browser':
check(true, shibaInu5 instanceof Dog5);
break;
case 'WSH-wsf':
check(false, shibaInu5 instanceof Dog5);
break;
}
//browser環境では動作するが、
//wsh-jscript環境では誤動作する
check(true, shibaInu5 instanceof ShibaInu5);
check(true, ShibaInu5 === shibaInu5.constructor);
}
//----------------------------------------
//このコードを6系列と呼ぶことにします。
// Animal6.call(this);
// Dog6.prototype.__proto__ = Animal6.prototype;
// Dog6.__proto__ = Animal6;
//この部分によって継承っぽい事を実現しています。
//結論としては、
//5系列と同じ結果です。
var test6 = function (testMode) {
//親クラス定義
function Animal6() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog6() {
Animal6.call(this);
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
Dog6.prototype.__proto__ = Animal6.prototype;
Dog6.__proto__ = Animal6;
//孫クラス定義
function ShibaInu6() {
Dog6.call(this);
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
ShibaInu6.prototype.__proto__ = Dog6.prototype;
ShibaInu6.__proto__ = Dog6;
//--------------------
//テストコード
//犬
var dog6 = new Dog6();
check('ワンワン', dog6.hello);
check(0, dog6.hand);
check(4, dog6.leg);
check('尻尾ある', dog6.tail);
check(true, typeof dog6.country === 'undefined');
check(true, dog6 instanceof Dog6);
check(true, Dog6 === dog6.constructor);
//柴犬
var shibaInu6 = new ShibaInu6();
check('柴犬だワン!', shibaInu6.hello);
check(0, shibaInu6.hand);
check(4, shibaInu6.leg);
check('尻尾ある', shibaInu6.tail);
check(false, typeof shibaInu6.country === 'undefined');
check('柴県出身です。', shibaInu6.country);
//これが理想形
//check(true, shibaInu6 instanceof Dog6);
switch (testMode) {
case 'browser':
check(true, shibaInu6 instanceof Dog6);
break;
case 'WSH-wsf':
check(false, shibaInu6 instanceof Dog6);
break;
}
check(true, shibaInu6 instanceof ShibaInu6);
check(true, ShibaInu6 === shibaInu6.constructor);
}
//----------------------------------------
//----------------------------------------
//このコードを7系列と呼ぶことにします。
// 継承用関数のinheritを定義していて
// 内部でObject.createを使っています。
// Animal7.call(this);
// Dog7.prototype = inherit(Animal7.prototype);
// Dog7.prototype.constructor = Dog7;
//この部分によって継承っぽい事を実現しています。
//結論としては、
//環境によらずに正しく動作しています。
//2系列と同じです
var test7 = function (testMode) {
//継承用関数
var inherit = function (p) {
if (Object.create) {
return Object.create(p);
}
function f() {};
f.prototype = p;
return new f();
}
//親クラス定義
function Animal7() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog7() {
Animal7.call(this);
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
Dog7.prototype = inherit(Animal7.prototype);
Dog7.prototype.constructor = Dog7;
//孫クラス定義
function ShibaInu7() {
Dog7.call(this);
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
ShibaInu7.prototype = inherit(Dog7.prototype);
ShibaInu7.prototype.constructor = ShibaInu7;
//--------------------
//テストコード
//犬
var dog7 = new Dog7();
check('ワンワン', dog7.hello);
check(0, dog7.hand);
check(4, dog7.leg);
check('尻尾ある', dog7.tail);
check(true, typeof dog7.country === 'undefined');
check(true, dog7 instanceof Dog7);
check(true, Dog7 === dog7.constructor);
//柴犬
var shibaInu7 = new ShibaInu7();
check('柴犬だワン!', shibaInu7.hello);
check(0, shibaInu7.hand);
check(4, shibaInu7.leg);
check('尻尾ある', shibaInu7.tail);
check(false, typeof shibaInu7.country === 'undefined');
check('柴県出身です。', shibaInu7.country);
check(true, shibaInu7 instanceof Dog7);
check(true, shibaInu7 instanceof ShibaInu7);
check(true, ShibaInu7 === shibaInu7.constructor);
}
//----------------------------------------
//このコードを8系列と呼ぶことにします。
// 継承用関数のinheritを定義していて
// 内部でsetPrototypeOfか、もしくは同等のコードを作っています。
// Animal8.call(this);
// inherits(Dog8, Animal8);
//この部分によって継承っぽい事を実現しています。
//結論としては、
//環境によらずに正しく動作しています。
//2系列/7系列と同じです
var test8 = function (testMode) {
//継承用関数(Google Closure Library 風)
var inherits = function(childCtor, parentCtor) {
// ES6
if (Object.setPrototypeOf) {
Object.setPrototypeOf(childCtor.prototype, parentCtor.prototype);
}
// ES5
else if (Object.create) {
childCtor.prototype = Object.create(parentCtor.prototype);
}
// legacy platform
else {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
}
};
//親クラス定義
function Animal8() {
this.hand = 0; //手
this.leg = 4; //足
this.hello = '...';
}
//子クラス定義
function Dog8() {
Animal8.call(this);
this.hello = 'ワンワン';
this.tail = '尻尾ある';
}
inherits(Dog8, Animal8);
//孫クラス定義
function ShibaInu8() {
Dog8.call(this);
this.hello = '柴犬だワン!';
this.country = '柴県出身です。';
}
inherits(ShibaInu8, Dog8);
//--------------------
//テストコード
//犬
var dog8 = new Dog8();
check('ワンワン', dog8.hello);
check(0, dog8.hand);
check(4, dog8.leg);
check('尻尾ある', dog8.tail);
check(true, typeof dog8.country === 'undefined');
check(true, dog8 instanceof Dog8);
check(true, Dog8 === dog8.constructor);
//柴犬
var shibaInu8 = new ShibaInu8();
check('柴犬だワン!', shibaInu8.hello);
check(0, shibaInu8.hand);
check(4, shibaInu8.leg);
check('尻尾ある', shibaInu8.tail);
check(false, typeof shibaInu8.country === 'undefined');
check('柴県出身です。', shibaInu8.country);
check(true, shibaInu8 instanceof Dog8);
check(true, shibaInu8 instanceof ShibaInu8);
check(true, ShibaInu8 === shibaInu8.constructor);
}
//----------------------------------------
test1(testMode);
test2(testMode);
test3(testMode);
switch (testMode) {
case 'browser':
test4(testMode);
break;
case 'WSH-wsf':
//WSHはこの実装は非対応なのでテストもなし
break;
}
test5(testMode);
test6(testMode);
test7(testMode);
test8(testMode);
alert('test finish');
};
if (typeof module !== 'undefined') {
module.exports = main;
}
細かいとこ、追記
・CScript/WScript 環境の JScript 用クラス名取得関数 classof()
https://gist.github.com/yu-tang/1690096
こちらのサイトをみると、wshで、constructor.name が取れないのに対応するためには
var arr = obj.constructor.toString().match(/function\s*(\w+)/);
if (arr && arr.length == 2) return arr[1];
このようなので取得するみたいです。
また、こちらにも、constructor.name への対応が載ってますね。
・[JavaScript] そんな継承はイヤだ - クラス定義 - オブジェクト作成 - Qiita
http://qiita.com/LightSpeedC/items/d307d809ecf2710bd957#%E4%BB%8A%E5%BA%A6%E3%81%AF-constructorname-%E3%81%AB%E3%82%88%E3%82%8B%E7%A2%BA%E8%AA%8D
以上、現場から、速報でした。