Приватні учасники в CoffeeScript?


84

Хтось знає, як зробити приватних, нестатичних членів у CoffeeScript? На даний момент я роблю це, що просто використовує загальнодоступну змінну, що починається з підкреслення, щоб пояснити, що її не слід використовувати поза класом:

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

  getName: -> @_name

Поміщення змінної в клас робить її статичним членом, але як я можу зробити її нестатичною? Чи можливо це взагалі без "фантазії"?

Відповіді:


20

Чи можливо це взагалі без "фантазії"?

Сумно говорити, що вам довелося б бути фантазією .

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

Пам'ятайте: "Це просто JavaScript".


1
... і тому ви повинні робити це так, як це робили б у JS. Легко це забути, коли він ховається за всім цим цукром, дякую!
thejh

4
Це насправді приватно? Ви все ще можете отримати доступ до нього поза класом. a = Thing ('a'), тоді a.getName () повертає значення, а a.getName = -> 'b' встановлює його.
Амір

4
@Amir: nameвидно лише зсередини закриття конструктора. Подивіться на цю суть: gist.github.com/803810
thejh

13
Також варто зазначити, що, @getName = -> nameздається, порушується будь-яке можливе успадкування getNameфункції.
Kendall Hopkins

12
Ця відповідь помилкова: тут getNameє загальнодоступним і nameдоступний лише з функції конструктора, тому насправді не є "приватним" для об'єкта.
tothemario

203

класи - це просто функції, тому вони створюють сфери. все, що визначено всередині цієї області, не буде видно ззовні.

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 компілює це в наступне:

(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
Слід зазначити , що ці приватні змінні НЕ доступні для підкласів.
Ceasar Bautista

45
Слід також зазначити, що foo.call(this)для того, thisщоб бути екземпляром функції, потрібно буде викликати подібні "приватні" методи . Ось чому спроба наслідувати класичне успадкування в JavaScript стає волохатою.
Джон Вінгфілд,

3
Ще одним недоліком є ​​те, що ви не матимете доступу до "приватних" методів для модульного тестування ..
nuc

16
Приватні методи @nuc - це деталі реалізації, які перевіряються за допомогою загальнодоступних методів, які їх викликають, тобто, приватні методи не повинні бути модульно перевірені. Якщо приватний метод здається, що він повинен бути перевірений одиницею, то, можливо, це повинен бути публічний метод. Побачити цей пост для гарного пояснення, а stackoverflow.com/questions/5750279 / ...
mkelley33

2
Слід також зазначити, що вам потрібно буде визначити ваші "приватні" змінні вище, де вони використовуються в "загальнодоступних" функціях. В іншому випадку CoffeeScript заплутається і створить нові внутрішні varдекларації, які відтінять їх.
Andrew Miner

11

Я хотів би показати щось ще вигадливіше

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

Тепер ви можете це зробити

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

Є одна проблема з відповіддю Віталія, і це те, що ви не можете визначити змінні, які ви хочете бути унікальними для області, якщо ви створили приватне ім’я таким чином, а потім змінили його, значення імені змінювалося б для кожного окремого екземпляра класу, отже, є один спосіб вирішити цю проблему

# 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

Неможливо отримати доступ до масиву імен ззовні, якщо ви не використовуєте getNames

Перевірте це

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

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

Я люблю цю реалізацію. Якісь недоліки?
Erik5388,

2

Ось рішення, яке спирається на кілька інших відповідей тут плюс https://stackoverflow.com/a/7579956/1484513 . Він зберігає змінні приватного екземпляра (нестатичні) у масиві приватного класу (статичний) і використовує ідентифікатор об’єкта, щоб знати, який елемент цього масиву містить дані, що належать кожному екземпляру.

# 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

Ось найкраща стаття я знайшов інформацію про налаштування public static members, private static members, public and private members, і деяких інших пов'язаних речах. Вона охоплює багато деталей і jsпроти coffeeпорівняння. І для історичних причин ось найкращий приклад коду з нього:

# 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

Ось як можна оголосити приватних, нестатичних членів у Coffeescript.
Повне посилання можна переглянути на 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

"клас" у кавових сценаріях призводить до результату на основі прототипу. Отже, навіть якщо ви використовуєте приватну змінну, вона ділиться між екземплярами. Ви можете зробити це:

EventEmitter = ->
  privateName = ""

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

.. веде до

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

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

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

Але будьте обережні, щоб ставити приватних членів перед публічними функціями, тому що кавовий скрипт повертає публічні функції як об’єкт. Подивіться на складений Javascript:

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

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

0

Оскільки сценарій кави компілюється до JavaScript, єдиним способом, яким ви можете мати приватні змінні, є закриття.

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

Це буде скомпільовано через такий 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

Звичайно, це має ті самі обмеження, що і всі інші приватні змінні, які ви можете отримати за допомогою закриття, наприклад, нещодавно додані методи не мають доступу до них, оскільки вони не були визначені в одній області дії.


9
Це свого роду статичний член. e = new Animal(5);f = new Animal(1);e.test()попереджає один, я хочу п’ять.
thejh

@thejh Ой, вибачте тоді, я зараз бачу помилку, думаю, було пізно думати про це вчора.
Ivo Wetzel

@thejh Це трапилося зі мною, я намагався вирішити цю проблему у своїй відповіді.
iConnor

0

Це нелегко зробити за допомогою класів CoffeeScript, оскільки вони використовують шаблон конструктора Javascript для створення класів.

Однак ви можете сказати щось подібне:

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

Але ви втрачаєте велич класів CoffeeScript, оскільки не можете успадкувати від класу, створеного таким чином, будь-яким іншим способом, окрім як за допомогою повторного використання extension (). instanceof перестане працювати, і об'єкти, створені таким чином, споживають трохи більше пам'яті. Крім того, ви більше не повинні використовувати нові та супер ключові слова.

Справа в тому, що закриття повинні створюватися кожного разу, коли клас створюється за допомогою екземпляра. Закриття членів у чистих класах CoffeeScript створюються лише один раз - тобто при побудові класу середовища виконання типу.


-3

Якщо ви хочете лише окремі приватні меми від загальнодоступних, просто оберніть їх у змінну $

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

і використовувати @$.requirements

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.