Acessando variáveis ​​de membro privadas a partir de funções definidas por protótipo

187

Existe alguma maneira de tornar variáveis ​​"privadas" (aquelas definidas no construtor) disponíveis para métodos definidos por protótipo?

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

Isso funciona:

t.nonProtoHello()

Mas isso não acontece:

t.prototypeHello()

Estou acostumado a definir meus métodos dentro do construtor, mas estou me afastando disso por alguns motivos.

morgancodes
fonte
14
@ecampver, exceto este foi perguntado 2 anos mais cedo ....
Pacerier

Respostas:

191

Não, não há como fazê-lo. Isso seria essencialmente o escopo inverso.

Os métodos definidos dentro do construtor têm 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 definidos no escopo do construtor e não terão acesso às variáveis ​​locais do construtor.

Você ainda pode ter variáveis ​​privadas, mas se quiser que os métodos definidos no protótipo tenham acesso a eles, defina getters e setters no thisobjeto, aos quais os métodos do protótipo (junto com todo o resto) terão 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()); };
Tríptico
fonte
14
"escopo reverso" é um recurso do C ++ com a palavra-chave "friend". Essencialmente, qualquer função deve definir seu protótipo como amigo. Infelizmente este conceito é C ++ e não JS :(
TWiStErRob
1
Gostaria de adicionar este post ao topo da minha lista de favoritos e mantê-lo lá.
Donato
2
Eu não vejo o ponto disso - você está apenas adicionando uma camada de abstração que não faz nada. Você também pode fazer secretuma propriedade de this. O JavaScript simplesmente não suporta variáveis ​​privadas com protótipos, pois os protótipos estão vinculados ao contexto do site de chamada, não ao contexto 'site de criação'.
precisa saber é o seguinte
1
Por que não fazer person.getSecret()então?
Fahmi
1
Por que isso tem tantos votos positivos? Isso não torna a variável privada. Como mencionado acima, usar person.getSecret () permitirá acessar essa variável privada de qualquer lugar.
alexr101
63

Atualização: Com o ES6, existe uma maneira melhor:

Para encurtar a história, você pode usar o novo Symbolpara criar campos particulares.
Aqui está uma ótima 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 defina as variáveis ​​privadas e funções públicas no fechamento, e todos os métodos públicos terão acesso privado às variáveis.

Ou você pode usar apenas protótipos

Em JavaScript, a herança prototípica é principalmente uma otimização . Ele permite que várias instâncias compartilhem métodos de protótipo, em vez de cada instância ter seus próprios métodos.
A desvantagem é que thisé a única coisa diferente sempre que uma função prototípica é chamada.
Portanto, todos os campos particulares devem ser acessíveis através this, o que significa que eles serão públicos. Portanto, mantemos as convenções de nomenclatura para _privatecampos.

Não se preocupe em misturar 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 outro.

Quando você usa um fechamento para acessar uma variável privada, os métodos de protótipo não podem acessar a variável. Então, você precisa expor o fechamento this, o que significa que você o está expondo publicamente de uma maneira ou de outra. Há muito pouco a ganhar com essa abordagem.

Qual eu escolho?

Para objetos realmente simples, basta usar um objeto simples com tampas.

Se você precisar de herança prototípica - por herança, desempenho etc. -, siga a convenção de nomenclatura "_private" e não se preocupe com os fechamentos.

Não entendo por que os desenvolvedores de JS se esforçam tanto para tornar os campos verdadeiramente privados.

Scott Rippey
fonte
4
Infelizmente, a _privateconvenção de nomenclatura ainda é a melhor solução se você quiser tirar proveito da herança prototípica.
esmagar
1
O ES6 terá um novo conceito, o Symbol, que é uma excelente maneira de criar campos particulares. Aqui está uma ótima explicação: curiosity-driven.org/private-properties-in-javascript
Scott Rippey
1
Não, você pode manter o Symbolfechamento que abrange toda a classe. Dessa forma, todos os métodos de protótipo podem usar o Symbol, mas ele nunca é exposto fora da classe.
Scott Rippey
2
O artigo é ligada diz " Símbolos são semelhantes aos nomes particulares, mas - ao contrário de nomes privados - eles não oferecem a verdadeira privacidade . ". Efetivamente, se você tiver a instância, poderá obter seus símbolos com Object.getOwnPropertySymbols. Portanto, isso é apenas privacidade pela obscuridade.
Oriol 25/05
2
@ Oriol Sim, a privacidade é via obscuridade pesada. Ainda é possível iterar pelos símbolos, e você deduz a finalidade do símbolo via toString. Isso não é diferente de Java ou C # ... membros privados ainda estão acessíveis via reflexão, mas geralmente são fortemente obscurecidos. Tudo isso reforça meu argumento final: "Não entendo por que os desenvolvedores de JS se esforçam tanto para tornar os campos verdadeiramente privados".
Scott Rippey
31

Quando li isso, parecia um desafio difícil, então decidi descobrir uma maneira. O que eu criei foi CRAAAAZY, mas funciona totalmente.

Primeiro, tentei definir a classe em uma função imediata para que você tivesse acesso a algumas das propriedades particulares dessa função. Isso funciona e permite que você obtenha alguns dados particulares; no entanto, se você tentar definir os dados particulares, em breve descobrirá que todos os objetos compartilharão 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);

Existem muitos casos em que isso seria adequado, como se você quisesse 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 precisar acessar variáveis ​​em um espaço para nome privado de dentro de seus métodos definidos no protótipo, tente 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 receber comentários de quem vê um erro nessa maneira de fazê-lo.

Mims H. Wright
fonte
4
Eu acho que uma preocupação em potencial é que qualquer instância possa acessar outros vars privados de instâncias usando um ID de instância diferente. Não necessariamente uma coisa ruim ...
Mims H. Wright
15
Você redefinir o protótipo de funções em cima de cada chamada do construtor
LU4
10
@ Lu4 Não sei se isso é verdade. O construtor é retornado de dentro de um fechamento; a única vez que as funções do protótipo são definidas é a primeira vez, nessa expressão de função imediatamente invocada. Questões de privacidade mencionadas acima à parte, isso me parece bom (à primeira vista).
guypursey
1
@ MimsH.Wright outros idiomas permitem o acesso a outros objetos privados da mesma classe , mas somente quando você faz referência a eles. Para permitir isso, você pode ocultar as partes privadas por trás de uma função que leva o ponteiro de objetos como a chave (ao contrário de um ID). Dessa forma, você só tem acesso a dados particulares de objetos que conhece, o que é mais alinhado com o escopo em outros idiomas. No entanto, essa implementação lança luz sobre um problema mais profundo com isso. Os objetos particulares nunca serão coletados como lixo até a função Construtor.
Thomas Nadin
3
Quero mencionar que ifoi adicionado a todas as instâncias. Portanto, não é totalmente "transparente" e iainda pode ser adulterado.
Scott Rippey
18

veja a página de Doug Crockford sobre isso . Você precisa fazer isso indiretamente com algo que possa 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
Jason S
fonte
47
Este exemplo parece ser uma prática terrível. O objetivo de usar métodos de protótipo é para que você não precise criar um novo para cada instância. Você está fazendo isso de qualquer maneira. Para cada método, você está criando outro.
Kir
2
@ArmedMonkey O conceito parece bom, mas concordamos que este é um mau exemplo, porque as funções de protótipo mostradas são triviais. Se as funções do protótipo fossem muito mais longas, exigindo acesso simples de obtenção / configuração às variáveis ​​'privadas', faria sentido.
Panqueca
9
Por que se incomodar em expor _setvia set? Por que não apenas nomeá-lo setpara começar?
Scott Rippey 16/02
15

Sugiro que provavelmente seria uma boa ideia descrever "ter uma atribuição de protótipo em um construtor" como um antipadrão de Javascript. Pense nisso. É muito arriscado.

O que você está fazendo ali na criação do segundo objeto (ou seja, b) está redefinindo a função protótipo para todos os objetos que usam esse protótipo. Isso redefinirá efetivamente o valor do objeto a no seu exemplo. Funcionará se você desejar uma variável compartilhada e se criar todas as instâncias de objeto antecipadamente, mas parecerá muito arriscado.

Eu encontrei um bug em algum Javascript em que eu estava trabalhando recentemente devido a esse exato anti-padrão. Ele estava tentando definir um manipulador de arrastar e soltar no objeto específico que estava sendo criado, mas estava fazendo isso em todas as instâncias. Não é bom.

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

Lance Ewing
fonte
10

@Kai

Isso não vai funcionar. Se você fizer

var t2 = new TestClass();

então t2.prototypeHelloacessará a seção privada de t.

@AnglesCrimes

O código de exemplo funciona bem, mas na verdade cria um membro privado "estático" compartilhado por todas as instâncias. Pode não ser a solução que os morgancodes procuraram.

Até agora, não encontrei uma maneira fácil e limpa de fazer isso sem introduzir um hash privado e funções de limpeza extras. Uma função de membro privado pode ser simulada 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;
}());
Tim
fonte
Entendeu seus pontos de vista claramente, mas você pode explicar o que seu snippet de código está tentando fazer?
Vishwanath
privateFooé completamente privado e, portanto, invisível ao obter um new Foo(). Somente aqui bar()é um método público que tem acesso privateFoo. Você pode usar o mesmo mecanismo para variáveis ​​e objetos simples, no entanto, sempre lembre-se de que eles privatessão realmente estáticos e serão compartilhados por todos os objetos que você criar.
Philzen
6

Sim é possivel. O padrão de design do PPF resolve isso.

PPF significa Funções de protótipo privadas. O PPF básico resolve esses problemas:

  1. As funções de protótipo obtêm acesso aos dados da instância privada.
  2. As funções de protótipo podem ser tornadas privadas.

Para o primeiro, apenas:

  1. Coloque todas as variáveis ​​de instância privadas que você deseja que sejam acessíveis a partir de funções de protótipo em um contêiner de dados separado e
  2. Passe uma referência ao contêiner de dados para todas as funções de protótipo como 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:

Padrão de Design PPF

Edward
fonte
4
A resposta somente de link geralmente é desaprovada no SO. Por favor, mostre um exemplo.
Corey Adler
O artigo tem exemplos no interior, por isso, vê lá
Edward
5
O que acontece, no entanto, se em algum momento mais tarde nesse site for interrompido? Como alguém deve ver um exemplo então? A política está em vigor para que qualquer coisa de valor em um link possa ser mantida aqui e não seja necessário confiar em um site que não esteja sob nosso controle.
Corey Adler
3
@ Edward, seu link é uma leitura interessante! No entanto, parece-me que o principal motivo para acessar dados privados usando funções prototípicas é impedir que todo objeto desperdice memória com funções públicas idênticas. O método que você descreve não resolve esse problema, pois para uso público, uma função prototípica precisa ser agrupada em uma função pública regular. Eu acho que o padrão pode ser útil para economizar memória se você tiver muitos ppf combinados em uma única função pública. Você os usa para mais alguma coisa?
Dining Philosopher
@DiningPhilosofer, obrigado por apreciar meu artigo. Sim, você está certo, ainda usamos funções de instância. Mas a idéia é tê-los o mais leve possível, apenas ligando novamente para seus colegas de PPF, que fazem todo o trabalho pesado. Eventualmente, todas as instâncias chamam os mesmos PPFs (por meio de wrappers, é claro), portanto, pode-se esperar uma certa economia de memória. A questão é quanto. Espero economia substancial.
Edward
5

Você pode realmente conseguir isso usando a Verificação de acessador :

(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 Prototípicas e Dados Privados e é explicado com mais detalhes por lá.

Chris West
fonte
1
Essa resposta é "inteligente" demais para ser útil, mas eu gosto da resposta de usar uma variável vinculada ao IFFE como um aperto de mão secreto. Essa implementação usa muitos fechamentos para ser útil; o objetivo de ter métodos definidos por protótipo é impedir a construção de novos objetos de função para cada método em cada objeto.
greg.kindel
Essa abordagem usa uma chave secreta para identificar quais métodos de protótipo são confiáveis ​​e quais não são. No entanto, é a instância que valida a chave; portanto, a chave deve ser enviada para a instância. Mas, então, o código não confiável poderia chamar um método confiável em uma instância falsa, o que roubaria a chave. E com essa chave, crie novos métodos que seriam considerados confiáveis ​​por instâncias reais. Portanto, isso é apenas privacidade pela obscuridade.
Oriol
4

Na atual JavaScript, estou bastante certo de que há uma e apenas uma maneira de ter estado privado , acessível a partir de protótipos de funções, sem acrescentar nada público para this. A resposta é usar o padrão "mapa fraco".

Para resumir: A Personclasse tem um único mapa fraco, onde as chaves são as instâncias de Person e os valores são objetos simples que são usados ​​para armazenamento privado.

Aqui está um exemplo totalmente funcional: (jogue 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, essa é realmente a única maneira de obter todas as três partes.

Existem duas advertências, no entanto. Primeiro, isso custa desempenho - sempre que você acessa os dados privados, é uma O(n)operação, onde nestá o número de instâncias. Portanto, você não vai querer fazer isso se tiver um grande número de instâncias. Segundo, quando você terminar uma instância, você deve chamar destroy; caso contrário, a instância e os dados não serão coletados como lixo e você terá um vazamento de memória.

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

Scott Rippey
fonte
Se você não destruir explicitamente uma instância de Person antes que ela saia do escopo, o mapa fraco não mantém uma referência a ela, para que haja um vazamento de memória? Eu criei um padrão para protected, pois outras instâncias do Person podem acessar a variável e as que herdam do Person. Apenas mexeu-lo então não tenho certeza se há alguma dis diferentes processamento extra vantagens (não olha tanto quanto acessando o privates) stackoverflow.com/a/21800194/1641941 Retornando um objeto private / protected é uma dor desde o código de chamada pode então mudar seu privado / protegido.
HMR 16/02
2
@HMR Sim, você precisa destruir explicitamente os dados privados. Vou acrescentar esta ressalva à minha resposta.
Scott Rippey
3

Há uma maneira mais simples, aproveitando o uso de binde callmétodos.

Ao definir variáveis ​​privadas para um objeto, você pode aproveitar o 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 apresenta desvantagens. Como o contexto do escopo está sendo efetivamente substituído, você não tem acesso fora do _privateobjeto. No entanto, não é impossível ainda dar acesso ao escopo do objeto de instância. Você pode passar no contexto do objeto ( this) como o segundo argumento para bindou callainda ter acesso aos seus valores públicos na função prototype.

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)
thgaskell
fonte
2
Por que alguém criaria uma cópia do método prototype em vez de apenas criar um método instanciado em primeiro lugar?
crush
3

Tente!

    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();
AlanNLohse
fonte
1
Isso depende caller, que é uma extensão dependente de implementação não permitida no modo estrito.
Oriol
1

Aqui está o que eu criei.

(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 dessa implementação é que ela redefine os protótipos em todas as instanciações.

Xeltor
fonte
Interessante, eu realmente gosto da tentativa e estava pensando na mesma coisa, mas você está certo que redefinir a função protótipo em cada instanciação é uma limitação bastante grande. Isso não se deve apenas ao desperdício de ciclos da CPU, mas porque, se você mudar o protótipo mais tarde, ele será "redefinido" de volta ao seu estado original, conforme definido no construtor na próxima instanciação: /
Niko Bellic
1
Isso não apenas redefiniu os protótipos, mas também define um novo construtor para cada instância. Portanto, as "instâncias" não são mais instâncias da mesma classe.
Oriol
1

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

Os protótipos JavaScript são dourados.

Redu
fonte
2
Eu acredito que é melhor não usar protótipo na função construtora, pois ela criará uma nova função toda vez que uma nova instância for criada.
Whamsicore 29/07
@whamsicore Sim, é verdade, mas neste caso é essencial, pois para cada objeto instanciado, precisamos organizar um fechamento compartilhado. Essa é a razão pela qual as definições de funções residem dentro do construtor e temos que consulte o SharedPrivate.prototypequanto this.constructor.prototypeEle não é grande coisa para redefinir GETP e SETP várias vezes ...
Redu
1

Estou atrasado para a festa, mas acho que posso contribuir. Aqui, verifique isso:

// 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 esse padrão de acessador de método . A idéia essencial é que tenhamos um fechamento , uma chave dentro do fechamento, e criamos um objeto privado (no construtor) que só pode ser acessado se você tiver a chave .

Se você estiver interessado, pode ler mais sobre isso no meu artigo . Usando esse método, você pode criar propriedades por objeto que não podem ser acessadas fora do fechamento. Portanto, você pode usá-los no construtor ou protótipo, mas não em nenhum outro lugar. Não vi esse método usado em nenhum lugar, mas acho que é realmente poderoso.

guitarino
fonte
0

Você não pode colocar as variáveis ​​em um escopo mais alto?

(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!');
    };
}))();
Ev Haus
fonte
4
Em seguida, as variáveis ​​são compartilhadas entre todas as instâncias do MyClass.
esmagar
0

Você também pode tentar adicionar o método não diretamente no protótipo, mas na função construtora como esta:

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!
Maciej Dzikowicki
fonte
0

Aqui está uma coisa que eu inventei ao tentar encontrar a solução mais simples para esse problema, talvez possa ser útil para alguém. Eu sou novo em javascript, portanto, pode 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();
V.Mihaly4
fonte
0

Enfrentei exatamente a mesma pergunta hoje e, depois de elaborar a resposta de primeira classe de Scott Rippey, criei uma solução muito simples (IMHO), compatível com ES5 e eficiente, que também é segura contra conflitos de nome (usar _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 sua opinião.

alexgirao
fonte
Aqui está uma referência: verifique a seção "Um passo mais perto". philipwalton.com/articles/…
jimasun 15/02
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()

Como é isso? Usando um acessador particular. Só permite que você obtenha as variáveis, embora não as defina, depende do caso de uso.

dylan0150
fonte
Isso não responder à pergunta de uma forma útil. por que você acredita que esta é a resposta? como isso funciona? Simplesmente dizer a alguém para alterar seu código sem qualquer contexto ou significado não os ajuda a aprender o que fizeram de errado.
GrumpyCrouton
Ele queria uma maneira de acessar variáveis ​​privadas ocultas de uma classe por meio de protótipos sem ter que criar essa variável oculta em todas as instâncias da classe. O código acima é um exemplo de método para fazer isso. Como isso não é uma resposta para a pergunta?
precisa saber é o seguinte
Eu não disse que não era uma resposta para a pergunta. Eu disse que não era uma resposta útil , porque não ajuda ninguém a aprender. Você deve explicar seu código, por que funciona, por que é o caminho certo para fazê-lo. Se eu fosse o autor da pergunta, não aceitaria sua resposta porque ela não incentiva o aprendizado, não me ensina o que estou fazendo de errado ou o que o código fornecido está fazendo ou como funciona.
GrumpyCrouton
0

Eu tenho uma solução, mas não tenho certeza se é sem falhas.

Para que funcione, você deve usar a seguinte estrutura:

  1. Use 1 objeto privado que contém todas as variáveis ​​privadas.
  2. Use 1 função de instância.
  3. Aplique um fechamento ao construtor e a todas as funções de protótipo.
  4. Qualquer instância criada é feita fora do fechamento 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 ela fornece uma função de instância "this.getPrivateFields" para acessar o objeto de variáveis ​​privadas "privateFields", mas essa função retornará apenas o objeto "privateFields" dentro do fechamento principal definido (também funções de protótipo usando "this.getPrivateFields "precisa ser definido dentro deste fechamento).

Um hash produzido durante o tempo de execução e difícil de ser adivinhado é usado como parâmetros para garantir que, mesmo que "getPrivateFields" seja chamado fora do escopo do fechamento, não retorne o objeto "privateFields".

A desvantagem é que não podemos estender o TestClass com mais funções de 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" funções privadas.

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

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

Estava brincando com isso hoje e essa foi a única solução que pude encontrar sem usar o Symbols. O melhor de tudo é que tudo pode ser completamente privado.

A solução é baseada em um carregador de módulo caseiro, que basicamente se torna o mediador de um cache de armazenamento privado (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')
Eladian
fonte
0

Sei que já faz mais de uma década que isso foi perguntado, mas acabei de pensar nisso pela enésima vez na vida de programador e encontrei uma possível solução que ainda não sei se ainda gosto totalmente. . Eu não vi essa metodologia documentada antes, então vou chamá-la de "padrão de dólar público / privado" ou _ $ / $ .

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 usa uma função ClassDefinition que retorna uma função Constructor que retorna um objeto Interface . O único método da interface é o $que recebe um nameargumento para chamar a função correspondente no objeto construtor, quaisquer argumentos adicionais passados ​​apósname são passados ​​na invocação.

A função auxiliar definida globalmente ClassValuesarmazena todos os campos em um objeto, conforme necessário. Ele define a _$função para acessá-los name. Isso segue um padrão curto de obtenção / configuração, portanto, se valuefor passado, ele será usado 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];
    }
  };
};

A função definida globalmente Interfacepega um objeto e um Valuesobjeto para retornar um _interfacecom uma única função $que examina objpara encontrar uma função nomeada após o parâmetro namee a invoca valuescomo o objeto com escopo definido . Os argumentos adicionais passados ​​para $serão passados ​​na chamada de 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;
};

Na amostra abaixo, ClassXé atribuído o resultado de ClassDefinition, qual é a Constructorfunção. Constructorpode receber qualquer número de argumentos. Interfaceé o que o código externo obtém após 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 faz sentido ter funções não prototipadas Constructor, embora você possa defini-las no corpo da função construtora. Todas as funções são chamadas com o padrão de dólar público this.$("functionName"[, param1[, param2 ...]]) . Os valores privados são acessados ​​com o padrão de dólar privado this._$("valueName"[, replacingValue]); . Como Interfacenão existe uma definição para _$, os valores não podem ser acessados ​​por objetos externos. Como o corpo de cada função prototipada thisé definido como o valuesobjeto em função $, você receberá exceções se chamar diretamente as funções irmãos do Constructor; o padrão _ $ / $ também precisa ser seguido nos corpos das funções prototipadas. Abaixo o uso da 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 de valores em classes totalmente prototipadas. Não sei se vou usar isso, nem se tem falhas, mas, ei, foi um bom quebra-cabeça!

JPortillo
fonte
0

ES6 WeakMaps

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

Nota: O uso do WeakMaps garante segurança contra vazamentos de memória , permitindo que o Garbage Collector identifique e descarte instâncias não utilizadas.

// 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 desse padrão pode ser encontrada aqui

colxi
fonte
-1

Você precisa alterar três itens no seu código:

  1. Substitua var privateField = "hello"por this.privateField = "hello".
  2. No protótipo, substitua privateFieldpor this.privateField.
  3. No não protótipo, substitua também privateFieldpor 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()
A-Sharabiani
fonte
this.privateFieldnão seria um campo privado. é acessível a partir do exterior:t.privateField
V. Rubinetti 16/10
-2

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

A variável estará visível para o método adicionado protótipo, mas todas as instâncias das funções acessarão a mesma variável 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();

Espero que isso possa ser útil.

AnjosCrimes
fonte