Частные участники в 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' устанавливает его.
Amir

4
@Amir: nameвиден только внутри закрытия конструктора. Взгляните на эту суть: gist.github.com/803810
thejh

13
Также стоит отметить, что это, @getName = -> nameпохоже, нарушает любое возможное наследование getNameфункции.
Кендалл Хопкинс,

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
Также следует отметить, что «частные» методы должны быть вызваны like foo.call(this), thisчтобы быть экземпляром функции. Вот почему попытка эмулировать классическое наследование в JavaScript становится непростой задачей.
Джон Вингфилд

3
Еще одним недостатком является то, что у вас не будет доступа к «частным» методам для модульного тестирования ..
nuc

16
Частные методы @nuc - это детали реализации, которые тестируются с помощью вызывающих их общедоступных методов, то есть частные методы не должны тестироваться модульно. Если кажется, что частный метод подлежит модульному тестированию, возможно, это должен быть общедоступный метод. См. Также этот пост для хорошего объяснения stackoverflow.com/questions/5750279/…
mkelley33

2
Также следует отметить, что вам необходимо определить свои «частные» переменные выше, где они используются в «общедоступных» функциях. В противном случае CoffeeScript запутается и создаст новые внутренние varобъявления, которые затенят их.
Эндрю Майнер

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'

Но будьте осторожны, чтобы поместить частные члены перед общедоступными функциями, потому что сценарий coffee возвращает общедоступные функции как объект. Взгляните на скомпилированный Javascript:

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

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

0

Поскольку сценарий coffee компилируется до 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 Ой, извини, теперь я вижу ошибку, думаю, вчера было слишком поздно думать об этом.
Иво Ветцель

@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, потому что вы не можете наследовать от класса, созданного таким образом, никаким другим способом, кроме как снова использовать extend (). instanceof перестанет работать, а созданные таким образом объекты потребляют немного больше памяти. Также нельзя использовать новые и супер ключевые слова .

Дело в том, что замыкания должны создаваться каждый раз, когда создается экземпляр класса. Замыкания членов в чистых классах CoffeeScript создаются только один раз - то есть, когда создается «тип» среды выполнения класса.


-3

Если вы хотите отделить только приватных участников от публичных, просто оберните их в $ variable

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

и использовать @$.requirements

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.