Private members in CoffeeScript?

OopInstance VariablesPrivate MembersCoffeescript

Oop Problem Overview


Does somebody know how to make private, non-static members in CoffeeScript? Currently I'm doing this, which just uses a public variable starting with an underscore to clarify that it shouldn't be used outside of the class:

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

  getName: -> @_name

Putting the variable in the class makes it a static member, but how can I make it non-static? Is it even possible without getting "fancy"?

Oop Solutions


Solution 1 - Oop

classes are just functions so they create scopes. everything defined inside this scope won't be visible from the outside.

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 compiles this into the following:

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

Solution 2 - Oop

> Is it even possible without getting "fancy"?

Sad to say, you'd have to be fancy.

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

Remember, "It's just JavaScript."

Solution 3 - Oop

I'd like to show something even fancier

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

Now you can do

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

Solution 4 - Oop

There is one problem with Vitaly's answer and that is you cannot define variables that you want to be unique to the scope, if you made a private name that way and then changed it, the name value would change for every single instance of the class, so there is one way we can solve that problem

# 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

It's not impossible to access the the names array from outside unless you use getNames

Test this out

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

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

Solution 5 - Oop

Here is a solution that draws on several of the other answers here plus https://stackoverflow.com/a/7579956/1484513. It stores the private instance (non-static) variables in a private class (static) array and uses an object ID to know which element of that array contains the data belonging to each 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

Solution 6 - Oop

Here's the best article I found about setting public static members, private static members, public and private members, and some other related stuff. It covers much details and js vs. coffee comparison. And for the historical reasons here's the best code example from it:

# 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

Solution 7 - Oop

Here is how you can declare private, non-static members in Coffeescript
For full reference, you can take a look at 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

Solution 8 - Oop

"class" in coffee scripts leads to an prototype based result. So even if you use a private variable it is shared between instances. You can do this:

EventEmitter = ->
  privateName = ""

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

.. leads to

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

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

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

But be careful to put the private members before the public functions, because coffee script returns the public functions as object. Look at the compiled Javascript:

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

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

Solution 9 - Oop

Since coffee script compiles down to JavaScript the only way you can have private variables is through closures.

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

This will compile down through the following 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

Of course this has all the same limitations as all the other private variables you can have through the use of closures, for example, newly added methods don't have access to them since they were not defined in the same scope.

Solution 10 - Oop

You can't do it easily with CoffeeScript classes, because they use the Javascript constructor pattern for creating classes.

However, you could say something like this:

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

But you lose the greatness of CoffeeScript classes, because you can't inherit from a class created that way by any other way than by using extend() again. instanceof will stop working, and objecs created this way consume a little bit more memory. Also, you mustn't use the new and super keywords anymore.

The point is, that the closures must be created every time a class is instantiated. The member closures in pure CoffeeScript classes are created only once - that is, when the class runtime "type" is constructed.

Solution 11 - Oop

If you want only separate private memebers from public, just wrap it in $ variable

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

and use @$.requirements

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionthejhView Question on Stackoverflow
Solution 1 - OopVitaly KushnerView Answer on Stackoverflow
Solution 2 - OopmatyrView Answer on Stackoverflow
Solution 3 - OopTim WuView Answer on Stackoverflow
Solution 4 - OopiConnorView Answer on Stackoverflow
Solution 5 - OopWazView Answer on Stackoverflow
Solution 6 - Oopplunntic iamView Answer on Stackoverflow
Solution 7 - OopHung VoView Answer on Stackoverflow
Solution 8 - OopStefan DohrenView Answer on Stackoverflow
Solution 9 - OopIvo WetzelView Answer on Stackoverflow
Solution 10 - OopJaakko SalomaaView Answer on Stackoverflow
Solution 11 - OopborovskyView Answer on Stackoverflow