JavaScript Diary

クラスの機能と関数の機能を併用 [ 2001/12/27 ]

2001/08/08 の日記 に書きましたが、Boolean, Number, String は「クラス」として使用することは勿論のこと、引数を基本データ型に変換するという「関数」としての機能も持ち合わせています。

今回はこの奇妙なクラス(と関数)を実装します。つまりどのようなクラス(関数)かというと、

var refNum = new Number( "3.14" ); // クラスとして機能 : 数値型のラッパーオブジェクトを生成
var valNum = Number( "3.14" );     //  関数 として機能 : 3.14 という数値(基本データ型)を返す

このような変なクラス(関数)です。

これを実装するには「クラスとして使用されたのか、関数として使用されたのか」を知る必要があります。
上記コードの見た目の違いは new があるかないかの違いですが、勿論これ自体を調べる手立てはありません。

ではどういった方法で判別するのかというと「クラスと関数はfunction文で定義する」、そして、どんな場所からでも this を参照できるという点を利用します。

「コンストラクタ関数」と「普通の関数」の内部で this を参照するとクラスと関数によって入っている中身が違うのが確認できます。しかしここでは this で判別するのではなく this.constructor という形で参照し判別します。

関数がクラスとして呼ばれた場合、this.constructor の中身は一意にそのクラス、つまりコンストラクタ関数への参照が格納されています。

関数として呼ばれた場合 this.constructor の中身は環境(ブラウザ+α)によって違ってきます。例えば
Navigator4 の場合、EventCapturer というクラス。
Netscape6 の場合、目に見えるのは Object(「目に見える」というのは alert等でtoString関数が返した文字列を調べたということで実際は Object ではなく恐らく陰でnativeなコードです)。
InternetExplorerの場合は未定義値(undefined)になります。

幸いなことに関数として呼ばれた場合、this.constructor が一つとしてその関数を返すことはなく、実にシンプルに実装出来ます。

前置きが長くなりましたが、プログラムは以下のようになります。

function Sample(){
    if( this.constructor === arguments.callee ){
        // クラスとして呼ばれたときの処理(※ return文を書かないこと)
    }else{
        // 関数として呼ばれたときの処理
    }
}

簡単ですね。これだけで良いんです。使用例は(内部の処理は任せるとして)下記の通りです。

var obj = new Sample();
var val = Sample();

ここで、あまり意味ないですが、Numberクラス、そしてNumber関数とほぼ同等のクラス(関数)を Number を使わずに実装したいと思います( JNumber )。

// class : JNumber( Object obj );
// function : number JNumber( Object obj );
// 
// 先ず、関数なのかクラスなのかを判別する
// 関数の場合、引数を数値型に変換したものを返す(returnする)
// クラスの場合、引数を数値型に変換したものをインスタンス変数valueに設定する
function JNumber( v ){
    if( arguments.callee === this.constructor ){
        // クラスとして使用されたときの処理
        
        // このクラスを関数として使用し、引数を数値型(number型)に変換したものを
        // valueプロパティに設定する
        this.value = v === void 0 ? 0 : arguments.callee( arguments[0] );
    }else{
        // 関数として使用されたときの処理
        
        if( v === void 0 ) return 0 ;
        switch( typeof v ){
            case "boolean" : return v ? 1 : 0 ;
            case "number" : return v ;
            case "string" : return parseFloat( v );
            case "function" : return 0/0 ;
            // ↓ちょっと手抜き。無限再帰に陥る可能性有
            case "object" : return arguments.callee( v.valueOf() );
            default : return 0/0 ;
        }
    }
}

// 文字列型参照
JNumber.prototype.toString = function(){ return this.value.toString(); };
// 文字列型を除く基本データ型参照
JNumber.prototype.valueOf = function(){ return this.value ; };

使用例はこちら