web-dev-qa-db-fra.com

Des membres privés dans CoffeeScript?

Quelqu'un sait-il comment créer des membres privés non statiques dans CoffeeScript? Actuellement, je fais cela, qui utilise simplement une variable publique commençant par un trait de soulignement pour clarifier qu'elle ne devrait pas être utilisée en dehors de la classe:

class Thing extends EventEmitter
  constructor: (@_name) ->

  getName: -> @_name

Mettre la variable dans la classe en fait un membre statique, mais comment puis-je la rendre non statique? Est-ce même possible sans devenir "fantaisiste"?

85
thejh

Est-ce même possible sans devenir "fantaisiste"?

Triste à dire, il faudrait être fantaisie.

class Thing extends EventEmitter
  constructor: (name) ->
    @getName = -> name

Rappelez-vous, "C'est juste du JavaScript."

20
matyr

les classes ne sont que des fonctions afin de créer des étendues. tout ce qui est défini à l'intérieur de cette portée ne sera pas visible de l'extérieur.

class Foo
  # this will be our private method. it is invisible
  # outside of the current scope
  foo = -> "foo"

  # this will be our public method.
  # note that it is defined with ':' and not '='
  # '=' creates a *local* variable
  # : adds a property to the class prototype
  bar: -> foo()

c = new Foo

# this will return "foo"
c.bar()

# this will crash
c.foo

coffeescript le compile comme suit:

(function() {
  var Foo, c;

  Foo = (function() {
    var foo;

    function Foo() {}

    foo = function() {
      return "foo";
    };

    Foo.prototype.bar = function() {
      return foo();
    };

    return Foo;

  })();

  c = new Foo;

  c.bar();

  c.foo();

}).call(this);
204
Vitaly Kushner

Je voudrais montrer quelque chose d'encore plus chic

class Thing extends EventEmitter
  constructor: ( nm) ->
    _name = nm
    Object.defineProperty @, 'name',
      get: ->
        _name
      set: (val) ->
        _name = val
      enumerable: true
      configurable: true

Maintenant vous pouvez faire

t = new Thing( 'Dropin')
#  members can be accessed like properties with the protection from getter/setter functions!
t.name = 'Dragout'  
console.log t.name
# no way to access the private member
console.log t._name
11
Tim Wu

Voici le meilleur article que j'ai trouvé sur la configuration de public static members, private static members, public and private members, et quelques autres choses liées. Il couvre beaucoup de détails et js contre coffee comparaison. Et pour les raisons historiques , voici le meilleur exemple de code:

# CoffeeScript

class Square

    # private static variable
    counter = 0

    # private static method
    countInstance = ->
        counter++; return

    # public static method
    @instanceCount = ->
        counter

    constructor: (side) ->

        countInstance()

        # side is already a private variable, 
        # we define a private variable `self` to avoid evil `this`

        self = this

        # private method
        logChange = ->
            console.log "Side is set to #{side}"

        # public methods
        self.setSide = (v) ->
            side = v
            logChange()

        self.area = ->
            side * side

s1 = new Square(2)
console.log s1.area()   # output 4

s2 = new Square(3)
console.log s2.area()   # output 9

s2.setSide 4            # output Side is set to 4
console.log s2.area()   # output 16

console.log Square.instanceCount() # output 2
2
plunntic iam

Il y a un problème avec la réponse de Vitaly et c'est que vous ne pouvez pas définir les variables que vous voulez être nique à la portée, si vous avez fait un nom privé de cette façon et que vous l'avez ensuite changé, la valeur du nom changerait pour chaque instance de la classe, il y a donc un moyen de résoudre ce problème

# create a function that will pretend to be our class 
MyClass = ->

    # this has created a new scope 
    # define our private varibles
    names = ['joe', 'jerry']

    # the names array will be different for every single instance of the class
    # so that solves our problem

    # define our REAL class
    class InnerMyClass 

        # test function 
        getNames: ->
            return names;

    # return new instance of our class 
    new InnerMyClass

Il n'est pas impossible d'accéder au tableau des noms de l'extérieur sauf si vous utilisez getNames

Testez cela

test = new MyClass;

tempNames = test.getNames()

tempNames # is ['joe', 'jerry']

# add a new value 
tempNames.Push 'john'

# now get the names again 
newNames = test.getNames();

# the value of newNames is now 
['joe', 'jerry', 'john']

# now to check a new instance has a new clean names array 
newInstance = new MyClass
newInstance.getNames() # === ['joe', 'jerry']


# test should not be affected
test.getNames() # === ['joe', 'jerry', 'john']

Javascript compilé

var MyClass;

MyClass = function() {
  var names;
  names = ['joe', 'jerry'];
  MyClass = (function() {

    MyClass.name = 'MyClass';

    function MyClass() {}

    MyClass.prototype.getNames = function() {
      return names;
    };

    return MyClass;

  })();
  return new MyClass;
};
2
iConnor

Voici une solution qui s'appuie sur plusieurs des autres réponses ici plus https://stackoverflow.com/a/7579956/148451 . Il stocke les variables d'instance privées (non statiques) dans un tableau de classe privée (statique) et utilise un ID d'objet pour savoir quel élément de ce tableau contient les données appartenant à chaque instance.

# Add IDs to classes.
(->
  i = 1
  Object.defineProperty Object.prototype, "__id", { writable:true }
  Object.defineProperty Object.prototype, "_id", { get: -> @__id ?= i++ }
)()

class MyClass
  # Private attribute storage.
  __ = []

  # Private class (static) variables.
  _a = null
  _b = null

  # Public instance attributes.
  c: null

  # Private functions.
  _getA = -> a

  # Public methods.
  getB: -> _b
  getD: -> __[@._id].d

  constructor: (a,b,@c,d) ->
    _a = a
    _b = b

    # Private instance attributes.
    __[@._id] = {d:d}

# Test

test1 = new MyClass 's', 't', 'u', 'v'
console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 t u v

test2 = new MyClass 'W', 'X', 'Y', 'Z'
console.log 'test2', test2.getB(), test2.c, test2.getD()  # test2 X Y Z

console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 X u v

console.log test1.a         # undefined
console.log test1._a        # undefined

# Test sub-classes.

class AnotherClass extends MyClass

test1 = new AnotherClass 's', 't', 'u', 'v'
console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 t u v

test2 = new AnotherClass 'W', 'X', 'Y', 'Z'
console.log 'test2', test2.getB(), test2.c, test2.getD()  # test2 X Y Z

console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 X u v

console.log test1.a         # undefined
console.log test1._a        # undefined
console.log test1.getA()    # fatal error
2
Waz

Voici comment déclarer des membres privés non statiques dans Coffeescript
Pour référence complète, vous pouvez consulter https://github.com/vhmh2005/jsClass

class Class

  # private members
  # note: '=' is used to define private members
  # naming convention for private members is _camelCase

  _privateProperty = 0

  _privateMethod = (value) ->        
    _privateProperty = value
    return

  # example of _privateProperty set up in class constructor
  constructor: (privateProperty, @publicProperty) ->
    _privateProperty = privateProperty
1
Hung Vo

"classe" dans les scripts de café conduit à un résultat basé sur un prototype. Ainsi, même si vous utilisez une variable privée, elle est partagée entre les instances. Tu peux le faire:

EventEmitter = ->
  privateName = ""

  setName: (name) -> privateName = name
  getName: -> privateName

.. mène à

emitter1 = new EventEmitter()
emitter1.setName 'Name1'

emitter2 = new EventEmitter()
emitter2.setName 'Name2'

console.log emitter1.getName() # 'Name1'
console.log emitter2.getName() # 'Name2'

Mais attention à faire passer les membres privés avant les fonctions publiques, car le script café renvoie les fonctions publiques en tant qu'objet. Regardez le Javascript compilé:

EventEmitter = function() {
  var privateName = "";

  return {
    setName: function(name) {
      return privateName = name;
    },
    getName: function() {
      return privateName;
    }
  };
};
1
Stefan Dohren

Étant donné que le script Coffee se compile en JavaScript, la seule façon d'avoir des variables privées est de fermer.

class Animal
  foo = 2 # declare it inside the class so all prototypes share it through closure
  constructor: (value) ->
      foo = value

  test: (meters) ->
    alert foo

e = new Animal(5);
e.test() # 5

Cela se compilera via le code JavaScript suivant:

var Animal, e;
Animal = (function() {
  var foo; // closured by test and the constructor
  foo = 2;
  function Animal(value) {
    foo = value;
  }
  Animal.prototype.test = function(meters) {
    return alert(foo);
  };
  return Animal;
})();

e = new Animal(5);
e.test(); // 5

Bien sûr, cela a les mêmes limites que toutes les autres variables privées que vous pouvez avoir grâce à l'utilisation de fermetures, par exemple, les méthodes nouvellement ajoutées n'y ont pas accès car elles n'étaient pas définies dans la même étendue.

0
Ivo Wetzel

Vous ne pouvez pas le faire facilement avec les classes CoffeeScript, car elles utilisent le modèle de constructeur Javascript pour créer des classes.

Cependant, vous pouvez dire quelque chose comme ceci:

callMe = (f) -> f()
extend = (a, b) -> a[m] = b[m] for m of b; a

class superclass
  constructor: (@extra) ->
  method: (x) -> alert "hello world! #{x}#{@extra}"

subclass = (args...) -> extend (new superclass args...), callMe ->
  privateVar = 1

  getter: -> privateVar
  setter: (newVal) -> privateVar = newVal
  method2: (x) -> @method "#{x} foo and "

instance = subclass 'bar'
instance.setter 123
instance2 = subclass 'baz'
instance2.setter 432

instance.method2 "#{instance.getter()} <-> #{instance2.getter()} ! also, "
alert "but: #{instance.privateVar} <-> #{instance2.privateVar}"

Mais vous perdez la grandeur des classes CoffeeScript, car vous ne pouvez pas hériter d'une classe créée de cette manière autrement qu'en utilisant à nouveau extend (). instanceof ne fonctionnera plus et les objets créés de cette façon consomment un peu plus de mémoire. De plus, vous ne devez plus utiliser les mots-clés new et super.

Le fait est que les fermetures doivent être créées chaque fois qu'une classe est instanciée. Les fermetures de membres dans les classes CoffeeScript pures ne sont créées qu'une seule fois, c'est-à-dire lorsque le "type" d'exécution de classe est construit.

0
Jaakko Salomaa