สมาชิกส่วนตัวใน 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ฟังก์ชันที่เป็นไปได้
Kendall Hopkins

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 ทำให้ยุ่งยาก
Jon Wingfield

3
ข้อเสียอีกประการหนึ่งคือคุณจะไม่สามารถเข้าถึงวิธีการ "ส่วนตัว" สำหรับการทดสอบหน่วย ..
nuc

16
วิธีการส่วนตัวของ @nuc คือรายละเอียดการใช้งานที่ได้รับการทดสอบผ่านวิธีการสาธารณะที่เรียกพวกเขาซึ่งกล่าวได้ว่าวิธีการส่วนตัวไม่ควรทดสอบหน่วย หากวิธีการส่วนตัวดูเหมือนว่าควรจะทดสอบหน่วยได้บางทีก็ควรเป็นวิธีสาธารณะ ดูโพสต์นี้สำหรับคำอธิบายที่ดีเช่นกันstackoverflow.com/questions/5750279/…
mkelley33

2
นอกจากนี้ควรสังเกตด้วยว่าคุณจะต้องกำหนดตัวแปร "ส่วนตัว" ของคุณที่ด้านบนซึ่งจะใช้ในฟังก์ชัน "สาธารณะ" มิฉะนั้น CoffeeScript จะสับสนและสร้างการvarประกาศภายในใหม่ซึ่งจะทำให้พวกเขาเป็นเงา
Andrew Miner

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

มีปัญหาหนึ่งในคำตอบของ 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;
};

ฉันชอบการใช้งานนี้ ข้อบกพร่องใด ๆ ?
Erik5388

2

นี่คือวิธีการแก้ปัญหาที่สามารถดึงหลายคำตอบอื่น ๆ ที่นี่บวกhttps://stackoverflow.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

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

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

แต่ระวังที่จะใส่สมาชิกส่วนตัวก่อนหน้าที่สาธารณะเพราะ coffee script ส่งคืนฟังก์ชันสาธารณะเป็นวัตถุ ดู Javascript ที่คอมไพล์แล้ว:

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

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

0

เนื่องจากสคริปต์กาแฟรวบรวมลงใน 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 โอ้ขอโทษด้วยฉันเห็นข้อผิดพลาดตอนนี้เดาว่ามันสายเกินไปที่จะคิดถึงเรื่องนี้เมื่อวานนี้
Ivo Wetzel

@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 เนื่องจากคุณไม่สามารถสืบทอดจากคลาสที่สร้างขึ้นด้วยวิธีอื่นใดนอกจากการใช้ expand () อีกครั้ง อินสแตนซ์ของจะหยุดทำงานและ objecs ที่สร้างขึ้นด้วยวิธีนี้จะใช้หน่วยความจำเพิ่มขึ้นเล็กน้อย นอกจากนี้คุณจะต้องไม่ใช้ใหม่และซุปเปอร์คีย์เวิร์ดอีกต่อไป

ประเด็นคือต้องสร้างการปิดทุกครั้งที่มีการสร้างอินสแตนซ์คลาส การปิดสมาชิกในคลาส CoffeeScript ล้วนถูกสร้างขึ้นเพียงครั้งเดียวนั่นคือเมื่อสร้างคลาสรันไทม์ "type"


-3

หากคุณต้องการเฉพาะ memebers ส่วนตัวที่แยกจากสาธารณะให้รวมไว้ในตัวแปร $

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

และใช้ @$.requirements

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.