194

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

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

これは機能します:

t.nonProtoHello()

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

t.prototypeHello()

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

4

25 に答える 25

200

いいえ、それを行う方法はありません。それは本質的に逆のスコープになります。

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

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

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

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()); };
于 2009-01-12T17:08:30.230 に答える
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);

この方法でエラーが発生した場合は、フィードバックをいただければ幸いです。

于 2012-12-13T09:31:04.837 に答える
19

この の 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
于 2009-01-12T17:08:52.910 に答える
15

「コンストラクターでプロトタイプを代入すること」を Javascript のアンチパターンとして説明するのは、おそらく良い考えだと思います。考えてみてください。それはリスクが高すぎる。

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

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

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

于 2010-11-26T12:12:39.660 に答える
10

@カイ

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

var t2 = new TestClass();

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

@AnglesCrimes

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

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

(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-02-24T08:00:39.813 に答える
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 設計パターン

于 2013-11-19T20:49:08.637 に答える
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());

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

于 2013-02-07T19:52:06.883 に答える
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

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

于 2015-08-21T12:40:16.193 に答える
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!
于 2015-01-27T06:09:51.037 に答える
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();
于 2016-02-18T12:52:14.257 に答える
0

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

/*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 でテスト済み。私はあなたの意見を読みたいです。

于 2016-06-06T01:50:34.947 に答える
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!');
    };
}))();
于 2014-11-21T23:22:27.990 に答える
0

ES6 ウィークマップ

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

このパターンのより詳細な説明は、ここで見つけることができます

于 2019-06-24T10:56:28.627 に答える
-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();

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

于 2009-11-24T13:48:55.853 に答える