Pergunta

Existe alguma maneira de fazer variáveis ??“privados” (aquelas definidas no construtor), disponível métodos para criar protótipos definidos?

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

Isso funciona:

t.nonProtoHello()

Mas isso não:

t.prototypeHello()

Estou acostumado a definir meus métodos dentro do construtor, mas estou afastando-se de que por dois motivos.

Foi útil?

Solução

Não, não há nenhuma maneira de fazê-lo. Isso seria essencialmente escopo em sentido inverso.

Os métodos definidos dentro do construtor tem acesso a variáveis ??privadas, porque todas as funções têm acesso ao escopo em que foram definidas.

Os métodos definidos em um protótipo não são definidas no âmbito do construtor, e não terá acesso a variáveis ??locais do construtor.

Você ainda pode ter variáveis ??privadas, mas se você quiser métodos definidos no protótipo de ter acesso a eles, você deve definir getters e setters no objeto this, que os métodos de protótipo (juntamente com tudo mais) vontade tem acesso. Por exemplo:

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

Outras dicas

Update: Com ES6, há uma maneira melhor:

Para encurtar a história, você pode usar o novo Symbol para criar campos particulares.
Aqui está uma grande descrição: https://curiosity-driven.org/private-properties-in-javascript

Exemplo:

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

Para todos os navegadores modernos com ES5:

Você pode usar apenas Closures

A maneira mais simples de construir objetos é evitar a herança prototípica completamente. Apenas definir as variáveis ??privadas e funções públicas dentro do fechamento, e todos os métodos públicos terão acesso privado para as variáveis.

Ou você pode usar apenas Protótipos

Em JavaScript, herança prototípica é principalmente uma otimização . Ele permite que várias instâncias de métodos de protótipo ação, ao invés de cada instância tem seus próprios métodos.
A desvantagem é que this é o única coisa que é diferente cada vez que uma função protótipo é chamado.
Portanto, quaisquer campos privados deve ser acessível através this, o que significa que eles vão ser públicos. Então, nós apenas furar a convenções de nomenclatura para campos _private.

Não se preocupe mistura Closures com Protótipos

Eu acho que você não deve misturar variáveis ??de fechamento com métodos de protótipo. Você deve usar um ou o outro.

Quando você usa um fecho para acessar uma variável privada, métodos de protótipo não pode acessar a variável. Então, você tem que expor o fechamento em this, o que significa que você está expondo publicamente de uma forma ou de outra. Há muito pouco a ganhar com esta abordagem.

O que eu escolho?

Para objetos muito simples, basta usar um objeto simples com encerramentos.

Se precisar de herança prototípica - por herança, desempenho, etc. -., Em seguida, ficar com o "_private" convenção de nomenclatura, e não se preocupam com o fechamento

Eu não entendo por que os desenvolvedores JS tentar tão duro para fazer campos realmente privado.

Quando li isso, soou como um desafio difícil, então eu decidi descobrir uma maneira. O que surgiu foi craaaazy , mas totalmente funciona.

Em primeiro lugar, eu tentei definir a classe em uma função imediata para que você teria acesso a algumas das propriedades privadas dessa função. Isso funciona e permite-lhe obter alguns dados privados, no entanto, se você tentar definir os dados privados em breve você vai achar que todos os objetos irão compartilhar o mesmo valor.

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

Há uma abundância de casos em que isso seria adequado como se você queria ter valores constantes como nomes de eventos que são compartilhados entre instâncias. Mas, essencialmente, eles agem como variáveis ??estáticas privadas.

Se você absolutamente precisa de acesso a variáveis ??em um espaço privado de dentro seus métodos definidos no protótipo, você pode tentar este padrão.

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

Eu adoraria algum feedback de quem vê um erro com esta maneira de fazê-lo.

Doug Crockford neste . Você tem que fazê-lo indiretamente com algo que pode acessar o escopo da variável privada.

outro exemplo:

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

caso de uso:

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

Eu sugiro que provavelmente seria uma boa idéia para descrever "ter um trabalho protótipo em um construtor" como um anti-padrão Javascript. Pense nisso. É muito arriscado.

O que você está realmente fazendo lá na criação do segundo objeto (ou seja, b) está redefinindo essa função protótipo para todos os objetos que utilizam esse protótipo. Isso efetivamente redefinir o valor para o objeto a no seu exemplo. Ele irá funcionar se você quiser uma variável compartilhada e se acontecer de você criar todas as instâncias de objetos na frente, mas ele se sente muito arriscado.

Eu encontrei um bug em algum Javascript que eu estava trabalhando recentemente que foi devido a este anti-padrão exato. Ele estava tentando definir um arrastar e soltar manipulador no objeto particular que está sendo criado, mas foi em vez fazê-lo para todas as instâncias. Não é bom.

A solução de Doug Crockford é o melhor.

@Kai

Isso não vai funcionar. Se você fizer

var t2 = new TestClass();

então t2.prototypeHello estarão acessando a seção privada de t.

@AnglesCrimes

O código de exemplo fino funciona, mas na verdade, cria um "estático" membro privada partilhada por todas as instâncias. Pode não ser os morgancodes solução procurada.

Até agora eu não encontrei uma maneira fácil e limpa de fazer isso sem a introdução de um hash privada e funções de limpeza extra. A função de membro privado pode ser simulado, até certo ponto:

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

Sim, é possível. padrão de design PPF apenas resolve este.

PPF está para eventos privados protótipo. PPF básica resolve esses problemas:

  1. funções protótipo tenha acesso aos dados de instância privada.
  2. funções de protótipos podem ser privadas.

Para o primeiro, apenas:

  1. Coloque todas as variáveis ??de instância privadas que você quer ser acessível a partir de funções protótipos dentro de um recipiente de dados separado, e
  2. Passe uma referência para o contêiner de dados para todas as funções de protótipo como um parâmetro.

É simples assim. Por exemplo:

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

...

Leia a história completa aqui:

PPF Design Pattern

Você pode realmente conseguir isso usando Accessor Verificação :

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

Este exemplo vem do meu post sobre Funções prototypal e dados privados e é explicado em mais detalhes lá.

Na atual JavaScript, estou bastante certo de que não é um e único maneira de ter Estado privada , acessível a partir de protótipo funções, sem acrescentar nada public para this. A resposta é usar o "fraco mapa" padrão.

Para resumir:. A classe Person tem um único mapa fraco, onde as chaves são os casos de Pessoa, e os valores são objetos simples que são usados ??para armazenagem privada

Aqui está um exemplo totalmente funcional: (play em 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];
        }
    };
}

Como eu disse, esta é realmente a única maneira de alcançar todas as 3 partes.

Há duas ressalvas, no entanto. Em primeiro lugar, este desempenho custos - cada vez que você acessar os dados privados, é uma operação O(n), onde n é o número de instâncias. Então você não vai querer fazer isso se você tiver um grande número de casos. Segundo, quando você é feito com uma instância, você deve chamar destroy; caso contrário, a instância e os dados não serão lixo coletado, e você vai acabar com um vazamento de memória.

E é por isso que a minha resposta original, "Você não deveria" , é algo que eu gostaria de manter.

Há uma maneira mais simples, aproveitando o uso de métodos bind e call.

Ao definir variáveis ??privadas para um objeto, você pode alavancar escopo desse objeto.

Exemplo

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

Este método não é sem inconvenientes. Uma vez que o contexto do escopo está efetivamente sendo substituído, você não tem acesso fora do objeto _private. No entanto, não é impossível que ainda dão acesso ao escopo do objeto de instância. Você pode passar no contexto do objeto (this) como o segundo argumento para bind ou call ainda ter acesso a ele de valores públicos na função protótipo.

Acessando valores públicos

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)

Experimente!

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

Aqui está o que eu vim acima com.

(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

O principal problema com esta implementação é que redefine os protótipos em cada instanciação.

Há uma maneira muito simples de fazer isso

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._

protótipos JavaScript são de ouro.

Eu estou atrasado para a festa, mas eu acho que posso contribuir. Aqui, vejam isto:

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

Eu chamo este método acessor padrão . A ideia essencial é que temos um fechamento , a chave dentro do fechamento, e criamos um objeto particular (no construtor) que pode só pode ser acessado se você tem a tecla .

Se você está interessado, você pode ler mais sobre isso em meu artigo . Usando este método, você pode criar por propriedades de objetos que não podem ser acessados ??fora do encerramento. Portanto, você pode usá-los no construtor ou protótipo, mas não em qualquer outro lugar. Eu não vi este método usado em qualquer lugar, mas eu acho que é realmente poderoso.

Aqui está algo que eu vim acima com ao tentar encontrar a solução mais simples para este problema, talvez pudesse ser útil a alguém. Eu sou novo para javascript, por isso, pode muito bem haver alguns problemas com o código.

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

Eu enfrentei a exata mesma pergunta hoje e depois elaborar sobre Scott Rippey resposta de primeira classe, eu vim com uma solução muito simples (IMHO) que seja compatível com ES5 e eficiente, também é seguro nome confronto (usando _private parece inseguro).

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

Testado com ringojs e nodejs. Estou ansioso para ler a sua opinião.

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

Como é isso? Usando um sistema de acesso privado. Apenas permite que você obtenha as variáveis ??embora não para defini-los, depende do caso de uso.

Eu tenho uma solução, mas não estou certo de que é sem falhas.

Para que ele funcione, você tem que usar a seguinte estrutura:

  1. Use um objeto particular que contém todas as variáveis ??privadas.
  2. Use uma função de instância.
  3. Aplicar um fecho para o construtor e todas as funções de protótipo.
  4. Qualquer instância criada é feito fora do encerramento definido.

Aqui está o código:

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

Como isso funciona é que ele fornece uma função de instância "this.getPrivateFields" para acessar o "privateFields" variáveis ??privadas objeto, mas esta função só retornará o objeto "privateFields" dentro do principal fechamento definido (também protótipo funções usando " this.getPrivateFields" necessidade de ser definido dentro deste fechamento).

Um hash produzido durante a execução e difícil de ser adivinhada é utilizado como parâmetros para se certificar de que, mesmo se "getPrivateFields" é chamado fora do âmbito de fechamento não retornará os "privateFields" objeto.

A desvantagem é que não podemos estender TestClass com mais funções protótipo fora do fechamento.

Aqui está um código de teste:

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

EDIT:. Usando este método, também é possível "definir" eventos privados

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

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

Foi brincar com isso hoje e esta foi a única solução que eu poderia encontrar sem o uso de símbolos. A melhor coisa sobre isso é que pode realmente tudo ser totalmente privado.

A solução é baseada em torno de um carregador do módulo homegrown que basicamente se torna o mediador para um cache de armazenagem privada (usando um mapa fraco).

   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')

Eu sei que tem sido mais de 1 década desde que este foi foi perguntado, mas eu só colocar o meu pensamento sobre isso para o n-th vez na minha vida programador, e encontrou uma solução possível que eu não sei se eu inteiramente gosto ainda. Eu não vi essa metodologia documentada antes, por isso vou chamá-lo de "padrão público / privado dólar" ou _ $ / $ padrão .

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

O conceito utiliza um ClassDefinition função que retorna um Construtor função que retorna um Interface objeto. O único método de interface é $ que recebe um argumento name para invocar a função correspondente no objecto construtor, quaisquer argumentos adicionais passados ??após name são passados ??na invocação.

As lojas ClassValues função auxiliar globalmente definidos todos os campos em um objeto, conforme necessário. Ele define a função _$ para acessá-los por name. Isto segue um padrão curto get / set por isso, se value for aprovada, ela será usada como o novo valor da variável.

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

      return values[name];
    }
  };
};

O Interface função definida globalmente leva um objeto e um objeto Values para retornar um _interface com um único $ função que examina obj para encontrar uma função chamada após o name parâmetro e invoca com values como o escopo objeto. Os argumentos adicionais passados ??para $ serão repassados ??a invocação da função.

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

No exemplo abaixo, ClassX é atribuído ao resultado de ClassDefinition, que é a função Constructor. Constructor pode receber qualquer número de argumentos. Interface é o código externo fica depois de chamar o construtor.

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

Não há nenhum ponto em ter funções não-protótipo em Constructor, embora você poderia defini-los no corpo função de construtor. Todas as funções são chamadas com o this.$("functionName"[, param1[, param2 ...]]) padrão dólar pública . Os valores particulares são acessados ??com o this._$("valueName"[, replacingValue]); padrão dólar privada . Como Interface não tem uma definição para _$, os valores não podem ser acessados ??por objetos externos. Desde this de cada corpo da função protótipo está definido para o objeto values em função $, você receberá exceções se você chamar funções irmão construtor diretamente; _ $ / $ padrão deve ser seguido em corpos de função protótipo também. Abaixo uso de amostra.

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

E a saída do console.

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

O _ $ / $ padrão permite total privacidade dos valores em aulas totalmente prototipados. Eu não sei se eu vou usar isso, nem se ele tem falhas, mas hey, foi um bom quebra-cabeça!

ES6 WeakMaps

Usando um padrão simples com base em ES6 WeakMaps é possível obter variáveis ??de membro privadas, acessível a partir das funções de protótipo .

Nota: O uso de WeakMaps garantias segurança contra vazamentos de memória , deixando o Garbage Collector identificar e instâncias não utilizadas de descarte.

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

Uma explicação mais detalhada deste padrão pode ser encontrada aqui

Você não pode colocar as variáveis ??em um escopo maior?

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

Você também pode tentar adicionar método não diretamente no protótipo, mas em função de construtor como este:

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!

Você precisa mudar 3 coisas no seu código:

  1. Substitua var privateField = "hello" com this.privateField = "hello".
  2. No protótipo substituir privateField com this.privateField.
  3. No não-protótipo também substituir privateField com this.privateField.

O código final seria o seguinte:

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

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

var t = new TestClass();

t.prototypeHello()

Você pode usar uma atribuição protótipo na definição construtor.

A variável será visível para o método protótipo adicional, mas todas as instâncias das funções irão acessar a mesma variável compartilhada.

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

Espero que este pode ser útil.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top