web-dev-qa-db-ja.com

プロトタイプ定義関数からプライベートメンバー変数にアクセスする

プロトタイプ定義のメソッドで使用できる「コンストラクターで定義された」変数を「プライベート」変数にする方法はありますか?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

これは動作します:

t.nonProtoHello()

しかし、これはそうではありません:

t.prototypeHello()

私はコンストラクタ内でメソッドを定義するのに慣れていますが、いくつかの理由でそれから離れています。

180
morgancodes

更新:ES6では、より良い方法があります。

要するに、新しいSymbolを使用してプライベートフィールドを作成できます。
これは素晴らしい説明です: https://curiosity-driven.org/private-properties-in-javascript

例:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

ES5を搭載した最新のブラウザーの場合:

クロージャだけを使用できます

オブジェクトを構築する最も簡単な方法は、プロトタイプの継承を完全に回避することです。クロージャ内でプライベート変数とパブリック関数を定義するだけで、すべてのパブリックメソッドは変数にプライベートアクセスできます。

または、プロトタイプのみを使用できます

JavaScriptでは、プロトタイプ継承は主に最適化です。各インスタンスが独自のメソッドを持つのではなく、複数のインスタンスがプロトタイプメソッドを共有できます。
欠点は、thisがプロトタイプ関数が呼び出されるたびに異なるonlyであることです。
したがって、プライベートフィールドはthisを介してアクセスできる必要があります。つまり、パブリックフィールドになります。したがって、_privateフィールドの命名規則に従うだけです。

クロージャーとプロトタイプを混在させないでください

あなたは閉鎖変数をプロトタイプメソッドと混合すべきではないと思います。どちらかを使用する必要があります。

クロージャーを使用してプライベート変数にアクセスする場合、プロトタイプメソッドは変数にアクセスできません。したがって、クロージャーをthisに公開する必要があります。つまり、何らかの方法で公開していることを意味します。このアプローチで得られるものはほとんどありません。

どちらを選択しますか?

本当にシンプルなオブジェクトの場合は、クロージャーを持つプレーンオブジェクトを使用します。

継承、パフォーマンスなどのためにプロトタイプの継承が必要な場合は、「_ private」命名規則を守り、クロージャーを気にしないでください。

JS開発者がフィールドを本当にプライベートにするためにSOを努力する理由がわかりません。

61
Scott Rippey

これを読んだとき、それは難しい挑戦のように聞こえたので、私は方法を見つけ出すことにしました。私が思いついたのはCRAAAAZYでしたが、完全に機能します。

最初に、即時関数でクラスを定義して、その関数のいくつかのプライベートプロパティにアクセスできるようにしました。これは機能し、いくつかのプライベートデータを取得できますが、プライベートデータを設定しようとすると、すべてのオブジェクトが同じ値を共有することがすぐにわかります。

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

インスタンス間で共有されるイベント名などの定数値が必要な場合など、これで十分な場合がたくさんあります。しかし、本質的には、プライベート静的変数のように機能します。

プロトタイプで定義されたメソッド内からプライベート名前空間の変数に絶対にアクセスする必要がある場合は、このパターンを試すことができます。

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

この方法でエラーを見つけた人からのフィードバックが欲しいです。

31
Mims H. Wright

これに関するDoug Crockfordのページ を参照してください。プライベート変数のスコープにアクセスできるものを使用して、間接的に行う必要があります。

もう一つの例:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

使用事例:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42
18
Jason S

Javascriptのアンチパターンとして「コンストラクターでプロトタイプの割り当てを行う」ことを説明することは、おそらく良い考えだと思います。考えてみてください。あまりにも危険です。

2番目のオブジェクト(つまりb)の作成時に実際にあなたがしていることは、そのプロトタイプを使用するすべてのオブジェクトのプロトタイプ関数を再定義することです。これにより、例のオブジェクトaの値が効果的にリセットされます。共有変数が必要な場合、およびすべてのオブジェクトインスタンスを前もって作成する場合に機能しますが、あまりにもリスクが大きいと感じています。

この正確なアンチパターンが原因で、最近取り組んでいたJavascriptのバグを見つけました。作成中の特定のオブジェクトにドラッグアンドドロップハンドラーを設定しようとしましたが、代わりにすべてのインスタンスに対して設定していました。良くない。

Doug Crockfordのソリューションが最適です。

15
Lance Ewing

@カイ

それは機能しません。もしあなたがそうするなら

var t2 = new TestClass();

t2.prototypeHelloはtのprivateセクションにアクセスします。

しゅう

サンプルコードは正常に機能しますが、実際にはすべてのインスタンスで共有される「静的な」プライベートメンバーを作成します。 morgancodesが探しているソリューションではないかもしれません。

これまでのところ、プライベートハッシュと追加のクリーンアップ関数を導入せずにこれを行う簡単でクリーンな方法は見つかりませんでした。プライベートメンバー関数は、ある程度までシミュレートできます。

(function() {
    function Foo() { ... }
    Foo.prototype.bar = function() {
       privateFoo.call(this, blah);
    };
    function privateFoo(blah) { 
        // scoped to the instance by passing this to call 
    }

    window.Foo = Foo;
}());
10
Tim

はい、可能です。 PPF設計パターンはこれを解決するだけです。

PPFはPrivate Prototype Functionsの略です。基本的なPPFはこれらの問題を解決します。

  1. プロトタイプ関数は、プライベートインスタンスデータにアクセスします。
  2. プロトタイプ関数はプライベートにすることができます。

最初に、ちょうど:

  1. プロトタイプ関数からアクセスできるようにするすべてのプライベートインスタンス変数を別のデータコンテナー内に配置し、
  2. データコンテナーへの参照をすべてのプロトタイプ関数にパラメーターとして渡します。

とても簡単です。例えば:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

詳細はこちらをご覧ください。

PPFデザインパターン

6
Edward

実際にこれを実現するには、Accessor Verificationを使用します:

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

この例は Prototypal Functions&Private Data に関する私の投稿からのものであり、そこでさらに詳しく説明されています。

5
Chris West

現在のJavaScriptでは、oneoneのみプライベート状態プロトタイプ関数からアクセス可能、何も追加せずにpublic からthisへ。答えは、「弱いマップ」パターンを使用することです。

要約すると、Personクラスには単一のウィークマップがあり、キーはPersonのインスタンスであり、値はプライベートストレージに使用されるプレーンオブジェクトです。

完全に機能する例を次に示します:( http://jsfiddle.net/ScottRippey/BLNVr/ で再生)

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.Push(instance);
            values.Push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

私が言ったように、これは本当に3つの部分すべてを達成する唯一の方法です。

ただし、2つの注意事項があります。まず、これはパフォーマンスを犠牲にします-プライベートデータにアクセスするたびに、O(n)操作です。ここで、nはインスタンスの数です。そのため、多数のインスタンスがある場合は、これを行いたくないでしょう。次に、インスタンスの処理が完了したら、destroyを呼び出す必要があります。そうしないと、インスタンスとデータはガベージコレクションされず、メモリリークが発生します。

そして、それが私の元の答え、「あなたはすべきではない」が私が固執したいものである理由です。

4
Scott Rippey

それを試してみてください!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();
3
AlanNLohse

bindおよびcallメソッドの使用を活用するより簡単な方法があります。

オブジェクトにプライベート変数を設定することにより、そのオブジェクトのスコープを活用できます。

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

この方法には欠点がないわけではありません。スコープコンテキストは事実上オーバーライドされるため、_privateオブジェクトの外部からはアクセスできません。ただし、インスタンスオブジェクトのスコープへのアクセスを許可することは不可能ではありません。オブジェクトのコンテキスト(this)をbindまたはcallの2番目の引数として渡すと、プロトタイプ関数のパブリック値に引き続きアクセスできます。

公共価値へのアクセス

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)
3
thgaskell

これが私が思いついたものです。

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

この実装の主な問題は、すべてのインスタンス化でプロトタイプを再定義することです。

1
Xeltor

これを行うには非常に簡単な方法があります

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

JavaScriptプロトタイプは黄金色です。

1
Redu

私はパーティーに遅れていますが、貢献できると思います。ここで、これをチェックしてください:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

このメソッドを呼び出しますアクセサーパターン。基本的な考え方は、closurekeyをクロージャー内に持ち、private object(コンストラクター内)は、keyがある場合にのみアクセスできます。

興味のある方は、これについてもっと詳しく読むことができます 私の記事 。このメソッドを使用すると、クロージャーの外部からアクセスできないオブジェクトごとのプロパティを作成できます。したがって、コンストラクタまたはプロトタイプで使用できますが、他の場所では使用できません。この方法はどこでも使用されていませんが、本当に強力だと思います。

1
guitarino

これが尋ねられてから10年以上経っていることは知っていますが、プログラマーの人生でn回目にこれを考えて、まだ完全に好きかどうかわからない解決策を見つけました。この方法論をこれまでに文書化したことはないので、「プライベート/パブリックドルパターン」または_ $/$ patternと名付けます。

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

コンセプトは、Interfaceオブジェクトを返すConstructor関数を返すClassDefinition関数を使用します。インターフェイスの唯一のメソッドは$で、name引数を受け取ってコンストラクターオブジェクト内の対応する関数を呼び出します。nameの後に渡される追加の引数は呼び出しで渡されます。

グローバルに定義されたヘルパー関数ClassValuesは、必要に応じてすべてのフィールドをanオブジェクトに格納します。 nameでアクセスする_$関数を定義します。これは短いget/setパターンに従うため、valueが渡されると、新しい変数値として使用されます。

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

グローバルに定義された関数InterfaceはオブジェクトとValuesオブジェクトを取り、objを調べてパラメーターnameにちなんで名付けられた関数を見つける単一の関数_interfaceを含む$を返します。 valuesscopedオブジェクトとして呼び出します。 $に渡される追加の引数は、関数呼び出しで渡されます。

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

以下のサンプルでは、​​ClassXは、ClassDefinition関数の結果であるConstructorの結果に割り当てられています。 Constructorは、任意の数の引数を受け取る場合があります。 Interfaceは、コンストラクターを呼び出した後に外部コードが取得するものです。

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

Constructorにプロトタイプ化されていない関数を追加しても意味はありませんが、コンストラクター関数の本体で定義することもできます。すべての関数は、public dollar patternthis.$("functionName"[, param1[, param2 ...]])で呼び出されます。プライベート値には、プライベートドルパターンthis._$("valueName"[, replacingValue]);を使用してアクセスします。 Interfaceには_$の定義がないため、外部オブジェクトは値にアクセスできません。プロトタイプ化された各関数本体のthisは、関数$valuesオブジェクトに設定されているため、コンストラクター兄弟関数を直接呼び出すと例外が発生します。 _ $/$ patternは、プロトタイプ化された関数本体でも従う必要があります。以下の使用例。

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

コンソール出力。

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

_ $/$ patternは、完全にプロトタイプ化されたクラスの値の完全なプライバシーを許可します。これを使用するかどうか、また欠陥があるかどうかはわかりませんが、それは良いパズルでした!

0
JPortillo

変数をより高いスコープに入れることはできませんか?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();
0
Ev Haus
var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.Push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

どうですか?プライベートアクセサーを使用します。ユースケースによって異なりますが、変数を設定することはできませんが、変数のみを取得できます。

0
dylan0150

今日これをいじくり回していましたが、これがシンボルを使用せずに見つけることができる唯一のソリューションでした。これについての最もよい事は、それが実際にすべて完全にプライベートになることができるということです。

ソリューションは、基本的にプライベートストレージキャッシュの仲介者となる自家製モジュールローダーに基づいています(弱いマップを使用)。

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.Push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')
0
Eladian

ES6 WeakMaps

ES6 WeakMapsに基づいた単純なパターンを使用することにより、プロトタイプ関数から到達可能なプライベートメンバー変数を取得できます

注:WeakMapsを使用すると、ガベージコレクターが未使用のインスタンスを識別して破棄できるため、メモリリークに対する安全性が保証されます

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

このパターンのより詳細な説明は here にあります。

0
colxi

プロトタイプではなく、次のようなコンストラクター関数にメソッドを直接追加することもできます。

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.Push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!
0

この問題の最も簡単な解決策を探しているときに私が思いついたものがあります。おそらくそれは誰かに役立つかもしれません。私はjavascriptを初めて使用するので、コードに問題がある可能性があります。

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();
0
V.Mihaly4

今日はまったく同じ質問に直面し、Scott Rippeyの一流の応答について詳しく説明した後、ES5と互換性があり効率的であり、名前衝突に対して安全である(_privateを使用すると安全ではないようです)非常にシンプルなソリューション(IMHO)を思い付きました。

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Ringojsおよびnodejsでテスト済み。私はあなたの意見を読みたいです。

0
alexgirao

解決策は1つありますが、問題がないかどうかはわかりません。

動作させるには、次の構造を使用する必要があります。

  1. すべてのプライベート変数を含むプライベートオブジェクトを1つ使用します。
  2. 1つのインスタンス関数を使用します。
  3. コンストラクターとすべてのプロトタイプ関数にクロージャーを適用します。
  4. 作成されたインスタンスはすべて、定義されたクロージャーの外部で実行されます。

コードは次のとおりです。

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

これがどのように機能するかは、「privateFields」プライベート変数オブジェクトにアクセスするインスタンス関数「this.getPrivateFields」を提供しますが、この関数は定義されたメインクロージャー内の「privateFields」オブジェクトのみを返します(「this.getPrivateFields 「このクロージャ内で定義する必要があります)。

実行時に生成され、推測が困難なハッシュは、「getPrivateFields」がクロージャのスコープ外で呼び出されても「privateFields」オブジェクトを返さないようにするためのパラメータとして使用されます。

欠点は、クロージャーの外側にプロトタイプ関数を追加してTestClassを拡張できないことです。

以下にテストコードを示します。

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

編集:この方法を使用して、プライベート関数を「定義」することもできます。

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};
0
Jannes Botis