Javascriptでprivateフィールドを実現する

 javascriptでprivate変数や、それに準ずるスコープを定義することは永遠の命題でした。

 アンダースコアを付けて規約で縛る方法やら、ややこしい実装でかえって可読性を犠牲にしているような方法まで。

 不可能であると結論づけたいところですが、無理やり実現する方法を考えてみます。

クロージャで定義する

var privateObject = (function(){
    var privateNumber = 0;

    return {
        set: function(number){
            privateNumber = number;
        },
        get: function(){
            return privateNumber;
        }
    }
})();

privateObject.set(1);
console.log(privateObject.get()); // 1

 これで、privateNumberに外部からアクセスする方法は無いにも関わらず、getter/setterを通じて自由に変更、読み取りを行うことができます。

 しかし、privateNumberは言わば常にstaticな変数であり、インスタンス変数として持つことが出来ないという点が問題です。

 これは、いわゆるシングルトン、staticなオブジェクトに有効な方法ですが、では、newを通じて作るインスタンスオブジェクトではどうでしょうか。

インスタンス作成も可能

 waniwaniさんより、コメントを頂きました。ありがとうございます!

 クロージャを使う場合でも、new演算子でインスタンスを生成することができるようです。private変数を求めていた方には朗報ですね。

function sample(num) {
  let number = num;
  return {
    get number(){ return number; }
  };
}

let s1 = new sample(1);
let s2 = new sample(2);
console.log(s1.number);  // -> 1
console.log(s2.number);  // -> 2

 注意点としては、prototypeではないため、継承するときに少し気を使わなければいけない点です。とはいえ、昨今の端末は大変リッチなので、オブジェクトに持ってしまっても問題はないでしょう。

// これは動かない
class extended extends sample{
  say(){
    console.log("hello!");
  }
}


let s3 = new extended(3);
s3.say(); // -> TypeError: s1.say is not a function

 上記コードは、当然ですがprototypeチェーンが切れてしまっているため、思うように動きません。なので、以下のようにします。

// これは動く。
function extended(num){
  let _super = new sample(num);
  _super.say = function(){
    console.log("hello!");
  };
  _super.sayNumber = function(){
    console.log(_super.number);
  };
  return _super;
}

let s4 = new extended(4);
s4.say(); // -> Hello!
s4.sayNumber(); // -> 4;

 カジュアルに使う分には、簡潔で非常に素晴らしいと思います。

 ただ、インスタンス毎にreturnしているオブジェクト内にfunctionが生成されるため、メモリを気にしなければならない場合は十分注意して使ってください。

配列を使う

 privateNumberの値が他インスタンスと共有されていなければよいと考えることができます。それなら配列を利用してみるのはどうでしょうか。

var privateMultiObject = (function(){
    var currentId = 0;
    var privateNumbers = [];

    var privateObject = function(){
        // constructor
        this.id = ++currentId;
        Object.freeze(this);
    }

    privateObject.prototype.setPrivateNumber = function(number){
        privateNumbers[this.id] = number;
    }

    privateObject.prototype.getPrivateNumber = function(){
        return privateNumbers[this.id];
    }

    return privateObject;
})();

var a = new privateMultiObject();
var b = new privateMultiObject();
a.setPrivateNumber(5);
b.setPrivateNumber(10);
console.log(a.getPrivateNumber());  // 5
console.log(b.getPrivateNumber());  // 10

a.id = 2
console.log(a.getPrivateNumber());  // 5

 この場合、this.idが変更されてしまうと他の変数を参照されたりしてしまうデメリットがあります。

 なので、本例では、Object.freeze()メソッドでオブジェクトを凍結し、外部から変更できないようにしています。

 ソースコードでidに2を代入(しようと)しているにも関わらず、返却される値はid=1の状態のものであることを確認してください。

 しかしながら、本例は真の意味でのprivate変数ではなく、言わば無理矢理に近い実装です。

 やはり、javascriptではその流儀に則り、このような小手先の技を使うことなく、利点と欠点を認識して使うことが大切だと思います。

 ご存知のとおり、Javascriptは自由度が高く、なんでも出来る言語です。もしかしたら、もっと良い方法があるかもしれませんし、無いかもしれません。是非いろいろ考えてみてください。

まとめ

 無理にprivate定義する必要があるかは悩ましいことではありますが、こういった問題も時には生じ得ることを踏まえ、言語仕様と仲良くやっていきたいものですね。

 また、今後はES6が流行してくることも予想されます。現在でもBabelをはじめ、様々なaltJSが存在しますので、他の言語のような記述を行いたい場合は、そちらも検討してみることをお勧めします。