可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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"?
回答1:
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."
回答2:
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);
回答3:
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
回答4:
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;
};
回答5:
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
回答6:
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
回答7:
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
回答8:
"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;
}
};
};
回答9:
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.
回答10:
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.
回答11:
If you want only separate private memebers from public, just wrap it in $ variable
$:
requirements:
{}
body: null
definitions: null
and use @$.requirements