Prywatni członkowie CoffeeScript?


84

Czy ktoś wie, jak tworzyć prywatne, niestatyczne elementy członkowskie w CoffeeScript? Obecnie robię to, co po prostu używa publicznej zmiennej zaczynającej się od podkreślenia, aby wyjaśnić, że nie powinno się jej używać poza klasą:

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

  getName: -> @_name

Umieszczenie zmiennej w klasie sprawia, że ​​jest ona statycznym składnikiem, ale jak mogę uczynić ją niestatyczną? Czy jest to w ogóle możliwe bez „wymyślenia”?

Odpowiedzi:


20

Czy jest to w ogóle możliwe bez „wymyślenia”?

Przykro mi mówić, musiałbyś być wyszukany .

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

Pamiętaj: „To tylko JavaScript”.


1
... więc musisz to zrobić tak jak w JS. Łatwo o tym zapomnieć, kiedy jest ukryty za całym tym cukrem, dzięki!
thejh

4
Czy to naprawdę prywatne? Nadal możesz uzyskać do niego dostęp poza zajęciami. a = Rzecz („a”), a następnie a.getName () zwraca wartość, a a.getName = -> „b” ją ustawia.
Amir,

4
@Amir: namejest widoczny tylko z wnętrza zamknięcia konstruktora. Spójrz na to sedno: gist.github.com/803810
thejh

13
Warto również zauważyć, że @getName = -> namewydaje się , że przerywa wszelkie możliwe dziedziczenie getNamefunkcji.
Kendall Hopkins

12
Ta odpowiedź jest błędna: tutaj getNamejest publiczna i namejest dostępna tylko z funkcji konstruktora, więc tak naprawdę nie jest „prywatna” dla obiektu.
tothemario

203

klasy to tylko funkcje, więc tworzą zakresy. wszystko zdefiniowane w tym zakresie nie będzie widoczne z zewnątrz.

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 kompiluje to w następujący sposób:

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

9
Należy zauważyć, że te zmienne prywatne nie są dostępne dla podklas.
Ceasar Bautista

45
Należy również zauważyć, że metody „prywatne” będą musiały nazywać się podobnie foo.call(this), thisaby były instancją funkcji. Dlatego próba emulacji klasycznego dziedziczenia w JavaScript robi się dziwna.
Jon Wingfield

3
Kolejnym minusem jest to, że nie będziesz mieć dostępu do „prywatnych” metod testowania jednostkowego ..
nuc

16
Metody prywatne @nuc to szczegóły implementacji, które są testowane za pomocą metod publicznych, które je wywołują, co oznacza, że ​​metody prywatne nie powinny być testowane jednostkowo. Jeśli wydaje się, że metoda prywatna powinna być testowana jednostkowo, być może powinna to być metoda publiczna. Zobacz ten post, aby uzyskać dobre wyjaśnienie stackoverflow.com/questions/5750279/ ...
mkelley33

2
Należy również zauważyć, że musisz zdefiniować swoje „prywatne” zmienne powyżej, gdzie są używane w funkcjach „publicznych”. W przeciwnym razie CoffeeScript będzie zdezorientowany i utworzy nowe wewnętrzne vardeklaracje, które będą ich cieniem.
Andrew Miner,

11

Chciałbym pokazać coś jeszcze bardziej wyszukanego

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

Teraz możesz to zrobić

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

2

Jest jeden problem z odpowiedzią Witalija, a mianowicie nie możesz zdefiniować zmiennych, które chcesz, aby były unikalne w zakresie, jeśli utworzyłeś prywatną nazwę w ten sposób, a następnie ją zmieniłeś, wartość nazwy zmieniłaby się dla każdej instancji klasy, więc istnieje jeden sposób rozwiązania tego problemu

# 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

Dostęp do tablicy nazw z zewnątrz nie jest niemożliwy, chyba że używasz getNames

Przetestuj to

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

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

Uwielbiam tę realizację. Jakieś wady?
Erik5388

2

Oto rozwiązanie, które czerpie z kilku innych odpowiedzi tutaj oraz https://stackoverflow.com/a/7579956/1484513 . Przechowuje zmienne instancji prywatnej (niestatyczne) w tablicy klasy prywatnej (statycznej) i używa identyfikatora obiektu, aby wiedzieć, który element tej tablicy zawiera dane należące do każdej instancji.

# 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

Oto najlepszy artykuł znalazłem na temat ustawiania public static members, private static members, public and private members, i kilka innych rzeczy związane. Obejmuje ona wiele szczegółów i jsvs. coffeeporównanie. A ze względów historycznych oto najlepszy przykład kodu:

# 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

1

Oto jak możesz zadeklarować prywatnych, niestatycznych członków w Coffeescript
Aby uzyskać pełne informacje, możesz spojrzeć na 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

„klasa” w skryptach kawy prowadzi do wyniku opartego na prototypie. Więc nawet jeśli używasz zmiennej prywatnej, jest ona współdzielona między instancjami. Możesz to zrobić:

EventEmitter = ->
  privateName = ""

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

.. prowadzi do

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

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

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

Uważaj jednak, aby umieścić członków prywatnych przed funkcjami publicznymi, ponieważ skrypt coffee zwraca funkcje publiczne jako obiekt. Spójrz na skompilowany JavaScript:

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

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

0

Ponieważ skrypt kawy kompiluje się do JavaScript, jedynym sposobem na posiadanie zmiennych prywatnych jest zamknięcie.

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

Spowoduje to kompilację za pomocą następującego kodu JavaScript:

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

Oczywiście ma to takie same ograniczenia, jak wszystkie inne zmienne prywatne, które można mieć dzięki zastosowaniu domknięć, na przykład nowo dodane metody nie mają do nich dostępu, ponieważ nie zostały zdefiniowane w tym samym zakresie.


9
To jest rodzaj statycznego członka. e = new Animal(5);f = new Animal(1);e.test()jeden alert, chcę pięć.
thejh

@thejh Och, przepraszam, widzę teraz błąd, myślę, że było za późno, aby myśleć o tym wczoraj.
Ivo Wetzel

@thejh To mi się przydarzyło, próbowałem rozwiązać ten problem w mojej odpowiedzi.
iConnor

0

Nie można tego łatwo zrobić z klasami CoffeeScript, ponieważ używają one wzorca konstruktora JavaScript do tworzenia klas.

Możesz jednak powiedzieć coś takiego:

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

Tracisz jednak wielkość klas CoffeeScript, ponieważ nie możesz dziedziczyć z klasy utworzonej w ten sposób w żaden inny sposób, niż przez ponowne użycie rozszerzania (). instanceof przestanie działać, a utworzone w ten sposób obiekty zajmują trochę więcej pamięci. Ponadto nie możesz już używać nowych i super słów kluczowych.

Chodzi o to, że zamknięcia muszą być tworzone za każdym razem, gdy tworzona jest instancja klasy. Zamknięcia elementów członkowskich w czystych klasach CoffeeScript są tworzone tylko raz - to znaczy podczas konstruowania „typu” środowiska wykonawczego klasy.


-3

Jeśli chcesz oddzielić tylko prywatnych członków od publicznych, po prostu zawiń to w zmienną $

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

I użyć @$.requirements

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.