Pregunta

¿Hay alguna forma de hacer que las variables "privadas" (las definidas en el constructor) estén disponibles para los métodos definidos por el prototipo?

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

Esto funciona:

t.nonProtoHello()

Pero esto no:

t.prototypeHello()

Estoy acostumbrado a definir mis métodos dentro del constructor, pero me estoy alejando de eso por un par de razones.

¿Fue útil?

Solución

No, no hay forma de hacerlo. Eso sería esencialmente un alcance inverso.

Los métodos definidos dentro del constructor tienen acceso a variables privadas porque todas las funciones tienen acceso al alcance en el que se definieron.

Los métodos definidos en un prototipo no están definidos dentro del alcance del constructor y no tendrán acceso a las variables locales del constructor.

Todavía puede tener variables privadas, pero si desea que los métodos definidos en el prototipo tengan acceso a ellas, debe definir captadores y definidores en el objeto this , que los métodos del prototipo (junto con todo lo demás) tendrá acceso. Por ejemplo:

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

Otros consejos

Actualización: con ES6, hay una mejor manera:

Para resumir, puede usar el nuevo Symbol para crear campos privados.
Aquí hay una gran descripción: https://curiosity-driven.org/private-properties-in-javascript

Ejemplo:

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 los navegadores modernos con ES5:

Puede usar solo cierres

La forma más sencilla de construir objetos es evitar por completo la herencia de prototipos. Simplemente defina las variables privadas y las funciones públicas dentro del cierre, y todos los métodos públicos tendrán acceso privado a las variables.

O puede usar solo prototipos

En JavaScript, la herencia de prototipos es principalmente una optimización . Permite que varias instancias compartan métodos prototipo, en lugar de que cada instancia tenga sus propios métodos.
El inconveniente es que this es lo único que es diferente cada vez que se llama a una función prototípica.
Por lo tanto, cualquier campo privado debe ser accesible a través de this , lo que significa que serán públicos. Así que nos limitamos a las convenciones de nomenclatura para los campos _private .

No te molestes en mezclar cierres con prototipos

Creo que no debería mezclar variables de cierre con métodos prototipo. Deberías usar uno u otro.

Cuando utiliza un cierre para acceder a una variable privada, los métodos prototipo no pueden acceder a la variable. Por lo tanto, debe exponer el cierre en this , lo que significa que lo está exponiendo públicamente de una forma u otra. Hay muy poco que ganar con este enfoque.

¿Cuál elijo?

Para objetos realmente simples, solo use un objeto simple con cierres.

Si necesita una herencia prototípica, por herencia, rendimiento, etc., entonces quédese con el " _private " convención de nomenclatura, y no te molestes con los cierres.

No entiendo por qué los desarrolladores de JS intentan TAN difícil hacer que los campos sean realmente privados.

Cuando leí esto, sonaba como un desafío difícil, así que decidí encontrar la manera. Lo que se me ocurrió fue CRAAAAZY pero funciona totalmente.

Primero, intenté definir la clase en una función inmediata para que tuviera acceso a algunas de las propiedades privadas de esa función. Esto funciona y le permite obtener algunos datos privados, sin embargo, si intenta establecer los datos privados, pronto encontrará que todos los objetos compartirán el mismo 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);

Hay muchos casos en los que esto sería adecuado, como si quisiera tener valores constantes como nombres de eventos que se comparten entre instancias. Pero esencialmente, actúan como variables privadas estáticas.

Si absolutamente necesita acceso a variables en un espacio de nombres privado desde sus métodos definidos en el prototipo, puede probar este patrón.

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

Me encantaría recibir comentarios de cualquiera que vea un error con esta forma de hacerlo.

consulte la página de Doug Crockford sobre esto . Tienes que hacerlo indirectamente con algo que pueda acceder al alcance de la variable privada.

otro ejemplo:

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

Sugiero que probablemente sea una buena idea describir "tener una asignación de prototipo en un constructor". como un antipatrón Javascript. Piénsalo. Es demasiado arriesgado.

Lo que realmente está haciendo allí al crear el segundo objeto (es decir, b) es redefinir esa función prototipo para todos los objetos que usan ese prototipo. Esto restablecerá efectivamente el valor para el objeto a en su ejemplo. Funcionará si desea una variable compartida y si crea todas las instancias de objetos por adelantado, pero se siente demasiado arriesgado.

Encontré un error en algunos Javascript en los que estaba trabajando recientemente debido a este antipatrón exacto. Intentaba establecer un controlador de arrastrar y soltar en el objeto particular que se estaba creando, pero en cambio lo hacía para todas las instancias. No es bueno.

La solución de Doug Crockford es la mejor.

@Kai

Eso no funcionará. Si lo haces

var t2 = new TestClass();

luego t2.prototypeHello accederá a la sección privada de t.

@AnglesCrimes

El código de muestra funciona bien, pero en realidad crea un "estático" miembro privado compartido por todas las instancias. Puede que no sea la solución que buscaban los morgancodes.

Hasta ahora no he encontrado una manera fácil y limpia de hacer esto sin introducir un hash privado y funciones de limpieza adicionales. Una función de miembro privado se puede simular en cierta medida:

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

Sí, es posible. El patrón de diseño PPF solo resuelve esto.

PPF son las siglas de Private Prototype Functions. El PPF básico resuelve estos problemas:

  1. Las funciones de prototipo obtienen acceso a datos de instancias privadas.
  2. Las funciones de prototipo pueden hacerse privadas.

Para el primero, solo:

  1. Coloque todas las variables de instancia privadas a las que desea que se pueda acceder desde funciones prototipo dentro de un contenedor de datos separado, y
  2. Pase una referencia al contenedor de datos a todas las funciones prototipo como parámetro.

Es así de simple. Por ejemplo:

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

...

Lea la historia completa aquí:

Patrón de diseño PPF

Realmente puede lograr esto utilizando Verificación de acceso :

(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 ejemplo proviene de mi publicación sobre Funciones prototípicas & amp; Datos privados y se explica con más detalle allí.

En JavaScript actual, estoy bastante seguro de que hay una y solo una forma de tener estado privado , accesible desde prototype , sin agregar nada public a this . La respuesta es usar el " mapa débil " patrón.

Para resumir: la clase Person tiene un solo mapa débil, donde las claves son las instancias de Person, y los valores son objetos simples que se utilizan para el almacenamiento privado.

Aquí hay un ejemplo completamente funcional: (jugar en 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 dije, esta es realmente la única forma de lograr las 3 partes.

Sin embargo, hay dos advertencias. Primero, esto cuesta rendimiento: cada vez que accede a los datos privados, es una operación O (n) , donde n es el número de instancias. Por lo tanto, no querrá hacer esto si tiene una gran cantidad de instancias. En segundo lugar, cuando haya terminado con una instancia, debe llamar a destroy ; de lo contrario, la instancia y los datos no se recolectarán basura, y terminará con una pérdida de memoria.

Y es por eso que mi respuesta original, " No deberías " , es algo a lo que me gustaría apegarme.

Hay una manera más simple de aprovechar el uso de los métodos bind y call .

Al establecer variables privadas en un objeto, puede aprovechar el alcance de ese objeto.

Ejemplo

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 no está exento de inconvenientes. Dado que el contexto del alcance se anula de manera efectiva, no tiene acceso fuera del objeto _private . Sin embargo, no es imposible dar acceso al alcance del objeto de instancia. Puede pasar en el contexto del objeto ( this ) como el segundo argumento para bind o call para tener acceso a sus valores públicos en el prototipo función.

Acceso a 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)

¡Pruébalo!

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

Esto es lo que se me ocurrió.

(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

el principal problema con esta implementación es que redefine los prototipos en cada instancia.

Hay una manera muy simple de hacer esto

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

Los prototipos de JavaScript son dorados.

Llego tarde a la fiesta, pero creo que puedo contribuir. Aquí, mira esto:

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

Llamo a este método patrón de acceso . La idea esencial es que tenemos un cierre , una clave dentro del cierre, y creamos un objeto privado (en el constructor) que puede solo se accederá si tiene la clave .

Si está interesado, puede leer más sobre esto en mi artículo . Con este método, puede crear propiedades por objeto a las que no se puede acceder fuera del cierre. Por lo tanto, puede usarlos en constructor o prototipo, pero no en ningún otro lugar. No he visto este método usado en ningún lado, pero creo que es realmente poderoso.

Aquí hay algo que se me ocurrió al tratar de encontrar la solución más simple para este problema, tal vez podría ser útil para alguien. Soy nuevo en JavaScript, por lo que podría haber algunos problemas con el 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();

Me enfrenté exactamente a la misma pregunta hoy y después de desarrollar la respuesta de primera clase de Scott Rippey, se me ocurrió una solución muy simple (IMHO) que es compatible con ES5 y eficiente, también es seguro para el choque de nombres (usando _private parece inseguro).

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Probado con ringojs y nodejs. Estoy ansioso por leer tu opinión.

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

¿Cómo es esto? Usando un descriptor de acceso privado. Solo le permite obtener las variables, aunque no establecerlas, depende del caso de uso.

Tengo una solución, pero no estoy seguro de que no tenga fallas.

Para que funcione, debe usar la siguiente estructura:

  1. Use 1 objeto privado que contenga todas las variables privadas.
  2. Use 1 función de instancia.
  3. Aplique un cierre al constructor y todas las funciones prototipo.
  4. Cualquier instancia creada se realiza fuera del cierre definido.

Aquí está el 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;
})();

Cómo funciona esto es que proporciona una función de instancia " this.getPrivateFields " para acceder a " privateFields " objeto de variables privadas, pero esta función solo devolverá " privateFields " objeto dentro del cierre principal definido (también las funciones prototipo que usan " this.getPrivateFields " deben definirse dentro de este cierre).

Un hash producido durante el tiempo de ejecución y difícil de adivinar se utiliza como parámetros para asegurarse de que incluso si "getPrivateFields" se llama fuera del alcance del cierre, no devolverá los "privateFields" objeto.

El inconveniente es que no podemos extender TestClass con más funciones prototipo fuera del cierre.

Aquí hay un código de prueba:

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

EDITAR: Usando este método, también es posible " definir " funciones privadas.

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

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

Estaba jugando con esto hoy y esta fue la única solución que pude encontrar sin usar Símbolos. Lo mejor de esto es que en realidad puede ser completamente privado.

La solución se basa en un cargador de módulos de cosecha propia que básicamente se convierte en el mediador de un caché de almacenamiento privado (usando un mapa débil).

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

Sé que ha pasado más de 1 década desde que me lo pidieron, pero solo pensé en esto por enésima vez en mi vida de programador, y encontré una posible solución que no sé si totalmente como todavía. No he visto esta metodología documentada antes, así que la llamaré el patrón de dólar privado / público. o _ $ / $ patrón .

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

El concepto utiliza una función ClassDefinition que devuelve una función Constructor que devuelve un objeto Interface . El único método de la interfaz es $ que recibe un argumento name para invocar la función correspondiente en el objeto constructor, cualquier argumento adicional pasado después de pasar name en la invocación.

La función auxiliar globalmente definida ClassValues ?? almacena todos los campos en un objeto según sea necesario. Define la función _ $ para acceder a ellos por name . Esto sigue un breve patrón get / set, por lo que si se pasa value , se usará como el nuevo valor variable.

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

      return values[name];
    }
  };
};

La función Interface definida globalmente toma un objeto y un objeto Values ?? para devolver un _interface con una sola función $ que examina obj para encontrar una función con el nombre del parámetro name y la invoca con valores como ámbito objeto. Los argumentos adicionales pasados ??a $ se pasarán en la invocación de la función.

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

En el siguiente ejemplo, ClassX se asigna al resultado de ClassDefinition , que es la función Constructor . Constructor puede recibir cualquier número de argumentos. Interface es lo que obtiene el código externo después de llamar al constructor.

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

No tiene sentido tener funciones no prototipadas en Constructor , aunque podría definirlas en el cuerpo de la función del constructor. Todas las funciones se invocan con el patrón de dólar público this. $ (& Quot; functionName " [, param1 [, param2 ...]]) . Se accede a los valores privados con el patrón de dólar privado this ._ $ (" valueName " [, reemplacingValue]); . Como Interface no tiene una definición para _ $ , los objetos externos no pueden acceder a los valores. Como cada cuerpo de función de prototipo este se establece en el objeto values ?? en la función $ , obtendrá excepciones si llama directamente a las funciones del hermano Constructor; el _ $ / $ patrón también debe seguirse en los cuerpos de función prototipados. Debajo del uso de muestra.

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

Y la salida de la consola.

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

El _ $ / $ patrón permite la privacidad total de los valores en clases completamente prototipadas. No sé si alguna vez usaré esto, ni si tiene fallas, pero bueno, ¡fue un buen rompecabezas!

ES6 WeakMaps

Al utilizar un patrón simple basado en ES6 WeakMaps es posible obtener variables de miembros privados, accesibles desde las funciones prototipo .

  

Nota: El uso de WeakMaps garantiza seguridad contra pérdidas de memoria , al permitir que el recolector de basura identifique y descarte las instancias no 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;
}());

Se puede encontrar una explicación más detallada de este patrón aquí

¿No puede poner las variables en un ámbito superior?

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

También puede intentar agregar un método no directamente en el prototipo, sino en la función de constructor 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!

Necesita cambiar 3 cosas en su código:

  1. Reemplace var privateField = " hello " con this.privateField = " hello " .
  2. En el prototipo, reemplace privateField con this.privateField .
  3. En el no prototipo, también reemplace privateField con this.privateField .

El código final sería el siguiente:

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

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

var t = new TestClass();

t.prototypeHello()

Puede usar una asignación de prototipo dentro de la definición del constructor.

La variable será visible para el método agregado del prototipo, pero todas las instancias de las funciones accederán a la misma variable COMPARTIDA.

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 esto pueda ser útil.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top