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


187

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

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

これは機能します:

t.nonProtoHello()

しかし、これはしません:

t.prototypeHello()

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



14
@ecampver、これは2年に尋ねられたことを除いて....
Pacerier

回答:


191

いいえ、それを行う方法はありません。それは本質的に逆にスコーピングです。

すべての関数はそれらが定義されたスコープにアクセスできるため、コンストラクター内で定義されたメソッドはプライベート変数にアクセスできます。

プロトタイプで定義されたメソッドは、コンストラクターのスコープ内で定義されておらず、コンストラクターのローカル変数にアクセスできません。

プライベート変数を引き続き使用できますが、プロトタイプで定義されたメソッドがそれらにアクセスできるようにする場合はthis、プロトタイプメソッド(およびその他すべて)がアクセスできる、オブジェクトのゲッターとセッターを定義する必要あります。例えば:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };

14
「逆スコーピング」は、「friend」キーワードを持つC ++機能です。本質的に、どの関数も、それがフレンドであるため、プロトタイプを定義する必要があります。残念なことに、この概念はJSではなくC ++です:(
TWiStErRob

1
この投稿をお気に入りリストの一番上に追加して、そこに保持したいと思います。
Donato

2
私はこれの要点を理解していません-あなたは何もしない抽象化の層を追加しているだけです。のsecretプロパティを作成することもできますthis。プロトタイプは「作成サイト」コンテキストではなく、呼び出しサイトコンテキストにバインドされるため、JavaScriptは単純にプロトタイプでプライベート変数をサポートしません。
nicodemus13 2017年

1
person.getSecret()それならどうして?
Fahmi

1
なぜこれは非常に多くの賛成票を持っているのですか?これは変数をプライベートにしません。上記のようにperson.getSecret()を使用すると、どこからでもそのプライベート変数にアクセスできます。
alexr101 2018

63

更新: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あるだけ原型関数が呼び出されるたびに違うもの。
したがって、プライベートフィールドはすべてを介してアクセスできる必要がありますthis。つまり、パブリックフィールドになります。したがって、_privateフィールドの命名規則に固執します。

クロージャーとプロトタイプを混同しないでください。

私はあなたが考えるべきではないプロトタイプの方法で閉鎖変数を混ぜます。どちらかを使用する必要があります。

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

どちらを選びますか?

本当に単純なオブジェクトの場合は、クロージャー付きのプレーンオブジェクトを使用してください。

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

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


4
悲しいことに、_privateプロトタイプの継承を利用したい場合は、命名規則が依然として最善の解決策です。
クラッシュ

1
ES6には、Symbolプライベートフィールドを作成するための優れた方法である新しいコンセプトがあります。ここでは偉大な説明があります:curiosity-driven.org/private-properties-in-javascript
スコットRippey

1
いいえ、Symbolクラス全体を含むクロージャにを保持できます。このように、すべてのプロトタイプメソッドはシンボルを使用できますが、クラスの外部に公開されることはありません。
Scott Rippey、2015

2
あなたがリンクした記事は、「シンボルはプライベート名に似ていますが、プライベート名とは異なり、真のプライバシーを提供しません」と述べています。事実上、インスタンスがあれば、でそのシンボルを取得できますObject.getOwnPropertySymbols。したがって、これはあいまいさによるプライバシーにすぎません。
Oriol

2
@Oriolええ、プライバシーは非常にあいまいです。シンボルを反復処理することは引き続き可能であり、を介してシンボルの目的を推測できます toString。これはJavaまたはC#と同じです...プライベートメンバーはリフレクションを介して引き続きアクセスできますが、通常は非常に不明瞭です。「JS開発者がフィールドを本当にプライベートにするために一生懸命努力する理由がわかりません。」
Scott Rippey、2015年

31

これを読んだときは大変なことだったので、どうしようかと考えました。私が思いついたのは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);

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


4
潜在的な懸念の1つは、どのインスタンスも別のインスタンスIDを使用して他のインスタンスのプライベート変数にアクセスできることだと思います。必ずしも悪いことではありません...
Mims H. Wright

15
あなたはすべてのコンストラクタ呼び出し時にプロトタイプの関数を再定義
LU4

10
@ Lu4それが本当かどうかはわかりません。コンストラクターはクロージャー内から返されます。プロトタイプ関数が定義されるのは、そのすぐに呼び出される関数式で初めてです。上記で言及したプライバシーの問題は別として、これは私には一見良さそうです(一見)。
guypursey 2013

1
@ MimsH.Wright他の言語では、同じクラスの他のオブジェクトのプライベートアクセスできますが、それらを参照している場合に限られます。これを可能にするために、オブジェクトポインターをキーとして取る関数の背後にプライベートを非表示にすることができます(IDのように)。この方法では、知っているオブジェクトのプライベートデータにのみアクセスできます。これは、他の言語のスコープとよりインラインになります。ただし、この実装により、これに関するより深い問題が明らかになります。コンストラクター関数が存在するまで、プライベートオブジェクトはガベージコレクションされません。
Thomas Nadin 2013年

3
iすべてのインスタンスに追加されたことを述べたいと思います。そのため、完全に「透過的」ではなく、i改ざんされる可能性があります。
Scott Rippey 14

18

これについては、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

47
この例はひどい習慣のようです。プロトタイプメソッドを使用するポイントは、インスタンスごとに新しいメソッドを作成する必要がないようにするためです。とにかくそれをやっています。すべてのメソッドについて、別のメソッドを作成しています。
Kir

2
@ArmedMonkeyコンセプトはしっかりしているように見えますが、示されているプロトタイプ関数は取るに足らないものであるため、これは悪い例であることに同意しました。プロトタイプ関数が「プライベート」変数への単純なget / setアクセスを必要とするはるかに長い関数である場合、それは理にかなっています。
パンケーキ

9
なぜ面倒なことで公開_setするのsetですか?名前を付けるだけsetではどうですか。
スコットリッピー、2014

15

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

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

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

ダグ・クロックフォードのソリューションは最高です。


10

@カイ

それはうまくいきません。もし、するなら

var t2 = new TestClass();

その後、t2.prototypeHellotのプライベートセクションにアクセスします。

あずきっく

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

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

(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;
}());

ポイントを明確に理解しましたが、あなたのコードスニペットが何をしようとしているのか説明できますか?
ビシュワナート2012年

privateFooは完全にプライベートなので、を取得しても見えませんnew Foo()bar()ここでは、にアクセスできるpublicメソッドのみですprivateFoo。単純な変数とオブジェクトに同じメカニズムを使用できますが、これらprivatesは実際には静的であり、作成したすべてのオブジェクトによって共有されることを常に覚えておく必要があります。
Philzen 2013年

6

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

PPFはプライベートプロトタイプ関数の略です。基本的な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設計パターン


4
リンクのみの回答は、一般的にSOで嫌がられます。例を示してください。
Corey Adler

記事には例が含まれていますので、そちらをご覧ください
Edward

5
しかし、後でそのサイトがダウンした場合はどうなりますか?では、誰かが例を見るとどうなるのでしょうか?このポリシーは、リンク内のすべての価値のあるものをここに保持できるように設定されており、これが当社の管理下にないウェブサイトに依存する必要はありません。
Corey Adler

3
@エドワード、あなたのリンクは面白い読み物です!ただし、プロトタイプ関数を使用してプライベートデータにアクセスする主な理由は、すべてのオブジェクトが同一のパブリック関数でメモリを浪費しないようにするためだと私には思われます。パブリックで使用する場合、プロトタイプの関数を通常のパブリック関数でラップする必要があるため、記述した方法ではこの問題は解決されません。単一のパブリック関数に結合された多くのppfがある場合、このパターンはメモリを節約するのに役立つと思います。他に何か使用しますか?
哲学者の食事

@DiningPhilosofer、私の記事に感謝してくれてありがとう。はい、そうです。インスタンス関数を使用しています。しかし、アイデアは、すべての重い作業を行うPPFの対応物を呼び出すだけで、それらをできるだけ軽量にすることです。最終的にはすべてのインスタンスが(もちろんラッパーを介して)同じPPFを呼び出すため、ある程度のメモリの節約が期待できます。問題はどのくらいかです。かなりの節約になると思います。
エドワード

5

これは実際に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());

この例は、プロトタイプ関数とプライベートデータに関する私の投稿に由来し、そこでさらに詳しく説明されています。


1
この答えはあまりにも「賢い」ので役に立ちませんが、秘密のハンドシェイクとしてIFFEにバインドされた変数を使用する答えが好きです。この実装は、あまりにも多くのクロージャを使用しているため、役に立ちません。プロトタイプで定義されたメソッドを持つことのポイントは、各オブジェクトの各メソッドに対する新しい関数オブジェクトの構築を防ぐことです。
greg.kindel 2013

このアプローチでは、秘密鍵を使用して、信頼できるプロトタイプメソッドと信頼できないプロトタイプメソッドを識別します。ただし、キーを検証するのはインスタンスであるため、キーをインスタンスに送信する必要があります。しかし、その後、信頼できないコードが偽のインスタンスで信頼できるメソッドを呼び出し、キーを盗む可能性があります。そして、そのキーを使用して、実際のインスタンスから信頼されていると見なされる新しいメソッドを作成します。したがって、これはあいまいさによるプライバシーにすぎません。
Oriol

4

現在JavaScriptでは、私はそこにあることをかなり確信している1一つだけ持つ方法プライベート状態からアクセス可能、プロトタイプ何も追加せずに、機能を公開するには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。そうしないと、インスタンスとデータがガベージコレクションされず、メモリリークが発生します。

それが私の最初の答えである「すべきではない」であり続けたい理由です。


Personのインスタンスがスコープ外になる前に明示的に破棄しない場合、weakmapはそれへの参照を保持しないので、メモリリークが発生しますか?Personの他のインスタンスが変数にアクセスでき、Personから継承するインスタンスが変数にアクセスできるため、私はプロテクトのパターンを思い付きました。ただ、余分な処理以外のDISの利点があるかどうかではないことを確認(陰部アクセスな限り見えない)ので、それをいじっstackoverflow.com/a/21800194/1641941プライベート/保護されたオブジェクトを返すには、コードを呼び出す以来の痛みですその後、あなたのプライベート/保護を変異させることができます。
HMR 2014

2
@HMRええ、個人データを明示的に破棄する必要があります。この警告を私の回答に追加します。
スコットリッピー2014

3

bindcallメソッドの使用を活用する簡単な方法があります。

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

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)を2番目の引数として渡すbindcall、プロトタイプ関数のパブリック値に引き続きアクセスできます。

公開値へのアクセス

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)

2
そもそもインスタンス化されたメソッドを単に作成するのではなく、なぜプロトタイプメソッドのコピーを作成するのでしょうか。
2015

3

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

    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();

1
これはに依存していますcaller。これは、strict-modeでは許可されていない実装依存の拡張機能です。
Oriol

1

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

(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

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


興味深いことに、私はこの試みが本当に好きで同じことを考えていましたが、インスタンス化のたびにプロトタイプ関数を再定義することはかなり大きな制限であるとあなたは正しいです。これは、それがCPUサイクルを無駄だという理由だけではなく、あなたが後でprototoypeを変更した場合、それはなるだろうので、次のインスタンス化時にコンストラクタで定義されて元の状態に戻っを「リセット」:/
ニコBellic

1
これはプロトタイプを再定義するだけでなく、各インスタンスの新しいコンストラクターを定義します。したがって、「インスタンス」は同じクラスのインスタンスではなくなりました。
Oriol

1

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

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プロトタイプは黄金です。


2
新しいインスタンスが作成されるたびに新しい関数が作成されるため、コンストラクター関数でプロトタイプを使用しない方が良いと思います。
whamsicore

@whamsicoreはいtrueですが、インスタンス化されたすべてのオブジェクトに対して共有クロージャーを配置する必要があるため、この場合は必須です。これは、関数の定義は、コンストラクタ内に存在する理由だと我々はを参照する必要がSharedPrivate.prototypethis.constructor.prototypeそれは... GETPとSETP複数回再定義する大したない
のRedu

1

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

// 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

このメソッドをアクセサパターンと呼びます。基本的なアイデアは、我々が持っているということであるクロージャキー閉鎖内部を、私たちが作成したプライベートオブジェクトあなたが持っている場合にのみアクセスすることができます(コンストラクタで)キーを

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


0

変数をより高いスコープに配置できませんか?

(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!');
    };
}))();

4
次に、変数はMyClassのすべてのインスタンス間で共有されます。
クラッシュ

0

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

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

私は今日もまったく同じ質問に直面し、Scott Rippeyのファーストクラスの応答について詳しく説明した後、ES5と互換性があり効率的である非常にシンプルなソリューション(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でテストされています。あなたの意見を読みたいです。


ここに参照があります:「一歩近づく」セクションをチェックしてください。philipwalton.com/articles/...
jimasun

0
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()

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


これは便利な方法で質問に答えるものではありませなぜこれが答えだと信じますか?それどのように機能しますか?コンテキストや意味を持たずにコード変更するように誰かに指示するだけでは、何が悪いのかを理解するのに役立ちません。
GrumpyCrouton 2017年

クラスのすべてのインスタンスで非表示の変数を作成せずに、プロトタイプを介してクラスの非表示のプライベート変数にアクセスする方法が必要でした。上記のコードは、そのようにする方法の例です。それが質問に対する答えではないのはなぜですか?
dylan0150 2017年

質問に対する答えではないとは言いませんでした。だれもが学ぶのを助けるものではないので、それは有用な答えではなかったと私は言いました。あなたはあなたのコード、なぜそれが機能するのなぜそれがそれを行う正しい方法であるのを説明するべきです。私が質問の作成者だった場合、それは学習を奨励しないため、私はあなたの答えを受け入れません。それは私が間違っていることや、与えられたコードが何をしているか、どのように機能するかを教えてくれません。
GrumpyCrouton 2017年

0

私には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

今日これで遊んでいました、そしてこれは私がシンボルを使わずに見つけることができた唯一の解決策でした。これについての最もよい事はそれが実際にすべて完全にプライベートでありえるということです。

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

   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

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

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オブジェクトを返すコンストラクター関数を返すClassDefinition関数を使用します。インターフェースの唯一のメソッドは、コンストラクターオブジェクトの対応する関数を呼び出すための引数を受け取ることです。その後に渡される追加の引数は、呼び出しで渡されます。$namename

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

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

      return values[name];
    }
  };
};

グローバルに定義された関数Interfaceは、オブジェクトとオブジェクトを受け取り、パラメーターにちなんで名付けられた関数を見つけるために検査する単一の関数でValuesを返し、スコープ付きオブジェクトとしてそれを呼び出します。に渡される追加の引数は、関数の呼び出し時に渡されます。_interface$objnamevalues$

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の結果に割り当てられてClassDefinitionConstructorます。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いない関数をで作成しても意味がありませんが、コンストラクター関数の本体で定義できます。すべての関数は、公開ドルパターンで 呼び出されますthis.$("functionName"[, param1[, param2 ...]])。非公開の値は、非公開のドルパターンで アクセスされますthis._$("valueName"[, replacingValue]);。にInterfaceは定義がないため_$、外部オブジェクトから値にアクセスすることはできません。プロトタイプ化thisされた各関数本体はfunction内のvaluesオブジェクトに設定されるため、$コンストラクター兄弟関数を直接呼び出すと例外が発生します。_ $ / $パターンがあまりにも試作関数本体に続いする必要があります。以下は使用例です。

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

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


0

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;
}());

このパターンの詳細については、こちらをご覧ください。


-1

コードで3つのことを変更する必要があります。

  1. 交換してくださいvar privateField = "hello"this.privateField = "hello"
  2. 試作品で交換してくださいprivateFieldthis.privateField
  3. 非プロトタイプでも交換してくださいprivateFieldthis.privateField

最終的なコードは次のようになります。

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

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()

this.privateFieldプライベートフィールドではありません。外部からアクセス可能:t.privateField
V.ルビネッティ2018年

-2

コンストラクター定義内でプロトタイプ割り当てを使用できます。

変数はプロトタイプが追加されたメソッドに表示されますが、関数のすべてのインスタンスは同じSHARED変数にアクセスします。

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

これがお役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.