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.



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

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.


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.


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

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.


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

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


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


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.


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)

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

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.


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.


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.


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

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!

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

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.


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.


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

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

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!


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


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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.