Private Mitglieder in CoffeeScript?

84

Weiß jemand, wie man private, nicht statische Mitglieder in CoffeeScript erstellt? Derzeit mache ich dies, wobei nur eine öffentliche Variable verwendet wird, die mit einem Unterstrich beginnt, um zu verdeutlichen, dass sie nicht außerhalb der Klasse verwendet werden sollte:

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

  getName: -> @_name

Wenn Sie die Variable in die Klasse einfügen, wird sie zu einem statischen Element. Wie kann ich sie jedoch nicht statisch machen? Ist es überhaupt möglich, ohne "ausgefallen" zu werden?

thejh
quelle

Antworten:

20

Ist es überhaupt möglich, ohne "ausgefallen" zu werden?

Traurig zu sagen, du müsstest schick sein .

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

Denken Sie daran: "Es ist nur JavaScript."

matyr
quelle
1
... und so müssen Sie es tun, wie Sie es in JS tun würden. Leicht zu vergessen, wenn es hinter all dem Zucker versteckt ist, danke!
Thejh
4
Ist das wirklich privat? Sie können auch außerhalb der Klasse darauf zugreifen. a = Thing ('a'), dann gibt a.getName () den Wert zurück und a.getName = -> 'b' setzt ihn.
Amir
4
@Amir: nameist nur innerhalb des Konstruktorverschlusses sichtbar. Schauen Sie sich das Wesentliche an: gist.github.com/803810
thejh
13
Erwähnenswert ist auch, dass @getName = -> nameeine mögliche Vererbung der getNameFunktion zu brechen scheint .
Kendall Hopkins
12
Diese Antwort ist falsch: Hier getNameist sie öffentlich und namenur über die Konstruktorfunktion zugänglich, sodass sie für das Objekt nicht wirklich "privat" ist.
Tothemario
203

Klassen sind nur Funktionen, sodass sie Bereiche erstellen. Alles, was in diesem Bereich definiert ist, ist von außen nicht sichtbar.

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 fasst dies wie folgt zusammen:

(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);
Vitaly Kushner
quelle
9
Es ist zu beachten, dass diese privaten Variablen für Unterklassen nicht verfügbar sind.
Ceasar Bautista
45
Es sollte auch darauf hingewiesen, dass ‚private‘ Methoden wie genannt werden müssen , foo.call(this)damit thisdie Instanz Funktion. Aus diesem Grund wird der Versuch, die klassische Vererbung in JavaScript zu emulieren, haarig.
Jon Wingfield
3
Ein weiterer Nachteil ist , dass Sie keinen Zugriff auf „private“ Methoden für Unit - Tests haben ..
Nuk
16
@nuc private Methoden sind Implementierungsdetails, die über die öffentlichen Methoden getestet werden, die sie aufrufen. Das heißt, private Methoden sollten nicht einheitlich getestet werden. Wenn eine private Methode als einheitlich testbar erscheint, sollte sie möglicherweise eine öffentliche Methode sein. Siehe diesen Beitrag für eine gute Erklärung auch stackoverflow.com/questions/5750279/…
mkelley33
2
Es sollte auch beachtet werden, dass Sie Ihre "privaten" Variablen oben definieren müssen, wo sie in "öffentlichen" Funktionen verwendet werden. Andernfalls wird CoffeeScript verwirrt und erstellt neue interne varDeklarationen, die sie beschatten.
Andrew Miner
11

Ich möchte etwas noch schickeres zeigen

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

Jetzt können Sie tun

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
Tim Wu
quelle
2

Es gibt ein Problem mit Vitalys Antwort: Sie können keine Variablen definieren, die für den Bereich eindeutig sein sollen. Wenn Sie auf diese Weise einen privaten Namen erstellt und diesen dann geändert haben, ändert sich der Namenswert für jede einzelne Instanz der Klasse. Es gibt also einen Weg, wie wir dieses Problem lösen können

# 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

Es ist nicht unmöglich, von außen auf das Names-Array zuzugreifen, es sei denn, Sie verwenden getNames

Testen Sie dies

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

Kompiliertes Javascript

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;
};
iConnor
quelle
Ich liebe diese Implementierung. Irgendwelche Nachteile?
Erik5388
2

Hier ist eine Lösung, die sich auf einige der anderen Antworten hier sowie auf https://stackoverflow.com/a/7579956/1484513 stützt . Es speichert die Variablen der privaten Instanz (nicht statisch) in einem Array der privaten Klasse (statisch) und verwendet eine Objekt-ID, um zu wissen, welches Element dieses Arrays die Daten enthält, die zu jeder Instanz gehören.

# 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
Waz
quelle
2

Hier ist der beste Artikel , den ich über Einstellung gefunden public static members, private static members, public and private members, und einige andere ähnliche Sachen. Es umfasst viele Informationen und jsvs. coffeeVergleich. Und aus historischen Gründen ist hier das beste Codebeispiel:

# 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
plunntic iam
quelle
1

So können Sie private, nicht statische Mitglieder in Coffeescript deklarieren
. Eine vollständige Referenz finden Sie unter 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
Hung Vo
quelle
1

"Klasse" in Kaffeeskripten führt zu einem prototypbasierten Ergebnis. Selbst wenn Sie eine private Variable verwenden, wird diese von Instanzen gemeinsam genutzt. Du kannst das:

EventEmitter = ->
  privateName = ""

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

.. führt zu

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

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

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

Achten Sie jedoch darauf, die privaten Mitglieder vor die öffentlichen Funktionen zu stellen, da das Kaffeeskript die öffentlichen Funktionen als Objekt zurückgibt. Schauen Sie sich das kompilierte Javascript an:

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

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

Da das Kaffeeskript bis auf JavaScript kompiliert wird, können Sie private Variablen nur über Schließungen haben.

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

Dies wird über das folgende JavaScript kompiliert:

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

Dies hat natürlich dieselben Einschränkungen wie alle anderen privaten Variablen, die Sie durch die Verwendung von Closures haben können. Beispielsweise haben neu hinzugefügte Methoden keinen Zugriff darauf, da sie nicht im selben Bereich definiert wurden.

Ivo Wetzel
quelle
9
Das ist eine Art statisches Mitglied. e = new Animal(5);f = new Animal(1);e.test()warnt eins, ich will fünf.
thejh
@thejh Oh, tut mir leid, ich sehe den Fehler jetzt, denke, es war zu spät, um gestern über dieses Zeug nachzudenken.
Ivo Wetzel
@thejh Das ist mir passiert, ich habe versucht, dieses Problem in meiner Antwort zu lösen.
iConnor
0

Mit CoffeeScript-Klassen ist dies nicht einfach, da sie das Javascript-Konstruktormuster zum Erstellen von Klassen verwenden.

Man könnte jedoch so etwas sagen:

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

Sie verlieren jedoch die Größe von CoffeeScript-Klassen, da Sie nicht von einer Klasse erben können, die auf diese Weise auf andere Weise als durch erneutes Verwenden von extens () erstellt wurde. instanceof funktioniert nicht mehr und auf diese Weise erstellte Objekte verbrauchen etwas mehr Speicher. Außerdem dürfen Sie die neuen und super Schlüsselwörter nicht mehr verwenden.

Der Punkt ist, dass die Abschlüsse jedes Mal erstellt werden müssen, wenn eine Klasse instanziiert wird. Die Elementabschlüsse in reinen CoffeeScript-Klassen werden nur einmal erstellt, dh wenn der Klassenlaufzeittyp "Typ" erstellt wird.

Jaakko Salomaa
quelle
-3

Wenn Sie nur private Mitglieder von öffentlichen trennen möchten, wickeln Sie sie einfach in $ variable ein

$:
        requirements:
              {}
        body: null
        definitions: null

und verwenden @$.requirements

Borovsky
quelle