CoffeeScript 中的私人成员?

Posted

技术标签:

【中文标题】CoffeeScript 中的私人成员?【英文标题】:Private members in CoffeeScript? 【发布时间】:2011-06-08 19:25:00 【问题描述】:

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

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

  getName: -> @_name

将变量放入类中使其成为静态成员,但如何使其成为非静态成员?有没有可能不被“看中”?

【问题讨论】:

【参考方案1】:

类只是函数,因此它们创建了作用域。在此范围内定义的所有内容都不会从外部可见。

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

【讨论】:

需要注意的是,这些私有变量可用于子类。 还应该注意,需要像foo.call(this) 一样调用“私有”方法,以便this 成为函数的实例。这就是为什么试图在 javascript 中模拟经典继承变得棘手的原因。 另一个缺点是您无法使用“私有”方法进行单元测试.. @nuc 私有方法是通过调用它们的公共方法进行测试的实现细节,也就是说私有方法不应该进行单元测试。如果私有方法看起来应该是可单元测试的,那么也许它应该是公共方法。请参阅这篇文章以获得很好的解释***.com/questions/5750279/… 还应该注意的是,您需要在“公共”函数中使用它们的地方定义您的“私有”变量。否则,CoffeeScript 会感到困惑并创建新的内部 var 声明,这将影响它们。【参考方案2】:

有没有可能不被“看中”?

遗憾的是,你必须是 fancy

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

记住,“这只是 JavaScript。”

【讨论】:

...所以你必须像在 JS 中那样做。当它隐藏在所有的糖后面时很容易忘记它,谢谢! 这真的是私人的吗?您仍然可以在课堂外访问它。 a=Thing('a') 然后 a.getName() 返回值并 a.getName = ->'b' 设置它。 @Amir: name 仅在构造函数闭包内部可见。看看这个要点:gist.github.com/803810 另外值得注意的是@getName = -> name似乎破坏了getName函数的任何可能继承。 这个答案是错误的:这里,getName 是公共的,而name 只能从构造函数访问,所以它不是真正的对象“私有”。【参考方案3】:

我想展示一些更漂亮的东西

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

【讨论】:

【参考方案4】:

Vitaly 的回答有一个问题,那就是你不能定义你希望在范围内唯一的变量,如果你以这种方式创建了一个私有名称然后更改了它,那么名称值类的每一个实例都会改变,所以有一种方法可以解决这个问题

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

【讨论】:

我喜欢这个实现。有什么缺点吗?【参考方案5】:

这是一个利用此处其他几个答案以及https://***.com/a/7579956/1484513 的解决方案。它将私有实例(非静态)变量存储在私有类(静态)数组中,并使用对象 ID 来了解该数组的哪个元素包含属于每个实例的数据。

# 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 我找到的关于设置public static membersprivate static memberspublic and private members 和其他一些相关内容的最佳文章。它涵盖了很多细节和jscoffee 的比较。出于历史的原因,这里有最好的代码示例:

# 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】:

这是在 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

【讨论】:

【参考方案8】:

咖啡脚本中的“class”导致基于原型的结果。因此,即使您使用私有变量,它也会在实例之间共享。你可以这样做:

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

【讨论】:

【参考方案9】:

由于咖啡脚本编译为 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

当然,这与您可以通过使用闭包获得的所有其他私有变量具有所有相同的限制,例如,新添加的方法无法访问它们,因为它们未在同一范围内定义。

【讨论】:

这是一种静态成员。 e = new Animal(5);f = new Animal(1);e.test() 提醒一个,我想要五个。 @thejh 哦,对不起,我现在看到了错误,估计昨天想这些东西已经太晚了。 @thejh 这发生在我身上,我试图在我的回答中解决这个问题。【参考方案10】:

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 将停止工作,并且以这种方式创建的对象会消耗更多内存。此外,您不能再使用 newsuper 关键字。

关键是,每次实例化类时都必须创建闭包。纯 CoffeeScript 类中的成员闭包只创建一次 - 即在构造类运行时“类型”时。

【讨论】:

【参考方案11】:

如果您只想将私有成员与公共成员分开,只需将其包装在 $ 变量中

$:
        requirements:
              
        body: null
        definitions: null

并使用@$.requirements

【讨论】:

以上是关于CoffeeScript 中的私人成员?的主要内容,如果未能解决你的问题,请参考以下文章

如何将dojo工具包与rails 3.1资产管道和coffeescript一起使用?

强制访问私人成员[重复]

在课堂外可见的私人成员

朋友 c++ 不与私人成员一起工作

教义存储库与私人成员

在不使用朋友类的情况下访问私人成员[重复]