CoffeeScript 中的私人成员? ?

有人知道如何在 CoffeeScript 中创建私有的非静态成员吗?目前我正在做这件事,它只是使用一个以下划线开头的公共变量来澄清它不应该在类之外使用:

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


getName: -> @_name

将变量放在类中使其成为静态成员,但是如何使其非静态呢?不变得“花哨”还有可能吗?

29620 次浏览

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.

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

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

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.

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

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

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

and use @$.requirements

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

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

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

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

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