Home / ぼやきごと / 2012-11-03
2012-11-03

JavaScript:列挙型(enum)もどきオブジェクト

最近、enchant.jsにちょっと興味を持ち、10年振りくらいにJavaScriptに触ってみています。
JavaScriptといえばこのサイトができて間もない頃にダイアログ(alert)を表示してドヤ顔してたことくらいしか覚えがないですが、知らない間に随分と進化したものです。

んで、とりあえず列挙型(enum)が欲しいなーってことで、列挙型もどきオブジェクトを生成する関数を作ってみました。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
-
!
-
!
 
-
|
|
|
|
-
|
|
|
|
!
-
|
!
|
-
|
|
|
|
|
|
|
|
|
|
|
!
-
-
|
!
-
|
!
|
|
|
-
-
|
-
|
!
!
|
|
-
-
|
|
|
!
!
|
|
!
|
-
|
|
|
|
!
-
|
|
|
!
|
-
|
|
|
|
!
-
|
|
|
!
!
// ruche.js
 
// ライブラリルート
var ruche = { };
 
(function() {
    "use strict";
 
    var slice = Array.prototype.slice;
 
    /**
     * 列挙オブジェクト作成クラス
     *
     * ruche.Enum 関数の呼び出しは
     * ruche.Enum.make 関数の呼び出しと同義。
     */
    ruche.Enum = function() {
        return ruche.Enum.make.apply(this, arguments);
    };
 
    /**
     * 列挙オブジェクトを作成する。
     *
     * 引数がない場合は空のオブジェクトを作成する。
     * 第1引数が関数でない場合は ruche.Enum.numeric 関数の呼び出しと同義。
     *
     * 第2引数が配列の場合はそれを列挙名の配列として扱う。
     * 第2引数が配列でない場合は第2引数以降を列挙名の羅列として扱う。
     *
     * @param {function} [valueMaker]
     *      第1引数にインデックス、第2引数に列挙名を受け取り、
     *      列挙値を作成して返す関数。
     */
    ruche.Enum.make = function(valueMaker) {
        if (arguments.length === 0) {
            return ruche.Enum.make(function() { return 0; });
        }
        if (typeof valueMaker !== 'function') {
            return ruche.Enum.numeric.apply(this, arguments);
        }
        var args = slice.call(arguments, 1);
        
        var names = [ ];
        if (args.length !== 0) {
            if (args[0] instanceof Array) {
                names = args[0];
            } else {
                names = args;
            }
        }
 
        var values = { };
        for (var i = 0, len = names.length; i < len; ++i) {
            values[names[i]] = {
                value: valueMaker(i, names[i]),
                enumerable: true,
                writable: false,
            };
        }
 
        return Object.create(null, values);
    };
 
    /**
     * インデックスを列挙値として持つ列挙オブジェクトを作成する。
     *
     * 第1引数が配列の場合はそれを列挙名の配列として扱う。
     * 第1引数が配列でない場合は引数群を列挙名の羅列として扱う。
     */
    ruche.Enum.numeric = function() {
        var args = slice.call(arguments);
        args.unshift(function(i) { return i; });
        return ruche.Enum.make.apply(this, args);
    };
 
    /**
     * 列挙名自体を列挙値として持つ列挙オブジェクトを作成する。
     *
     * 第1引数が配列の場合はそれを列挙名の配列として扱う。
     * 第1引数が配列でない場合は引数群を列挙名の羅列として扱う。
     */
    ruche.Enum.named = function() {
        var args = slice.call(arguments);
        args.unshift(function(i, name) { return name.toString(); });
        return ruche.Enum.make.apply(this, args);
    };
})();

ちゃんと列挙値プロパティが定義されているか確認してみます。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
-
-
-
|
|
|
-
|
|
|
!
|
!
|
-
|
!
|
|
-
|
!
|
|
-
!
|
|
-
|
|
!
|
|
|
|
!
window.onload = function() {
    // プロパティ一覧を ul リストとして body 直下に追加
    var printPropList = function(value) {
        console.log(value);
 
        var ul = document.createElement('ul');
        for (var v in value) {
            var li = document.createElement('li');
            li.innerHTML = v + " = " + value[v];
            ul.appendChild(li);
        }
        document.body.appendChild(ul);
    };
 
    // ruche.Enum 呼び出しは ruche.Enum.make 呼び出しと同義
    // 第1引数が関数でなければ ruche.Enum.numeric 呼び出しと同義
    var Color = ruche.Enum('Red', 'Green', 'Blue');
    printPropList(Color);
 
    // 列挙名は引数として羅列する他に配列での指定も可能
    // ruche.Enum.numeric では列挙名のインデックスが列挙値となる
    var Num = ruche.Enum.numeric([ 'One', 'Two', 'Three' ]);
    printPropList(Num);
    
    // ruche.Enum.named では列挙名そのものが列挙値となる
    var Score = ruche.Enum.named('Bad', 'Good', 'Best');
    printPropList(Score);
 
    // ruche.Enum.make の第1引数に関数を指定することで
    // 独自の列挙値を持たせることも可能
    // 関数には第1引数にインデックス、第2引数に列挙名が渡される
    var animalValues = [ 'Dog', 'Cat', 'Lion' ];
    var Animal = ruche.Enum.make(
        function(i, name) { return i + "_" + name; },
        animalValues);
    printPropList(Animal);
};

上記のコードを埋め込んだHTML js_test/enum_test.html を確認してみてください。
それなりに新しいブラウザでないと動きません。

まぁ、言ってしまえば次のように書くのとほとんど同じです。

var Color = {
    Red:   0,
    Green: 1,
    Blue:  2,
};
var Score = {
    Bad:  'Bad',
    Good: 'Good',
    Best: 'Best',
};

ただ、今回の関数で作成した列挙値は書き換え不可の値になるので、誤って列挙値を上書きしてしまうようなことは防げます。

var Color = ruche.Enum('Red', 'Green', 'Blue');
Animal.Red = 100; // Color.Red == 0 のまま

この列挙値もどきオブジェクトは例えば次のような感じで使えます。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 
-
!
 
-
|
-
|
|
|
!
|
!
 
-
|
|
|
|
!
var Animal = ruche.Enum('Dog', 'Cat', 'Lion');
 
// 列挙値は writable=false なので、代入等しても値は変化しない
Animal.Dog = 'Tiger'; // Animal.Dog == 0 のまま
 
var printVoice = function(animal) {
    var voice = '………';
    switch (animal) {
        case Animal.Dog:  voice = 'わんわん'; break;
        case Animal.Cat:  voice = 'にゃーん'; break;
        case Animal.Lion: voice = 'がおー!'; break;
    }
    document.body.innerHTML += '<p>「' + voice + '」</p>';
};
 
window.onload = function() {
    printVoice(Animal.Dog);
    printVoice(Animal.Cat);
    printVoice(2);
    printVoice('Tiger');
};

上記のコードを埋め込んだHTML js_test/enum_animal.html を確認してみてください。

var ANIMAL_DOG = 0; みたいな定数もどきを使うよりはコードが見やすくなるのではないでしょうか。

Category: [JavaScript][プログラミング] - 2012-11-03 03:31:57