Thành viên tư nhân trong CoffeeScript?


84

Có ai biết cách tạo thành viên riêng tư, không tĩnh trong CoffeeScript không? Hiện tại tôi đang làm điều này, chỉ sử dụng một biến công khai bắt đầu bằng dấu gạch dưới để làm rõ rằng nó không nên được sử dụng bên ngoài lớp:

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

  getName: -> @_name

Đặt biến trong lớp làm cho nó trở thành một thành viên tĩnh, nhưng làm cách nào để biến nó thành một thành viên tĩnh? Nó thậm chí có thể mà không nhận được "ưa thích"?

Câu trả lời:


20

Nó thậm chí có thể mà không nhận được "ưa thích"?

Thật đáng buồn khi phải nói rằng, bạn phải trở nên ưa thích .

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

Hãy nhớ rằng, "Nó chỉ là JavaScript."


1
... và vì vậy bạn phải làm điều đó như khi làm trong JS. Dễ dàng quên nó khi nó ẩn sau tất cả những đường đó, cảm ơn!
thejh

4
Đó là thực sự riêng tư? Bạn vẫn có thể truy cập nó bên ngoài lớp học. a = Thing ('a') thì a.getName () trả về giá trị và a.getName = -> 'b' đặt nó.
Amir

4
@Amir: namechỉ hiển thị từ bên trong bao đóng hàm tạo. Nhìn vào ý chính này: gist.github.com/803810
thejh,

13
Cũng đáng lưu ý rằng @getName = -> namedường như phá vỡ bất kỳ kế thừa có thể có của getNamehàm.
Kendall Hopkins

12
Câu trả lời này sai: Ở đây, getNamelà công khai và namechỉ có thể truy cập từ hàm khởi tạo, vì vậy nó không thực sự là "riêng tư" đối với đối tượng.
tothemario

203

các lớp chỉ là các hàm để chúng tạo ra phạm vi. mọi thứ được xác định bên trong phạm vi này sẽ không hiển thị từ bên ngoài.

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 biên dịch điều này thành sau:

(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
Cần lưu ý rằng các biến private này không có sẵn cho các lớp con.
Ceasar Bautista

45
Cũng cần phải lưu ý rằng phương pháp 'tư nhân' sẽ cần phải được gọi là như thế foo.call(this)để cho thislà ví dụ của hàm. Đây là lý do tại sao cố gắng mô phỏng kế thừa cổ điển trong JavaScript trở nên khó khăn.
Jon Wingfield

3
Một nhược điểm khác là bạn sẽ không có quyền truy cập vào các phương pháp "riêng tư" để kiểm tra đơn vị ..
nuc

16
@nuc private method là các chi tiết triển khai được kiểm tra thông qua các phương thức public gọi chúng, có nghĩa là các phương thức private không nên được kiểm tra đơn vị. Nếu một phương thức private có vẻ như nó phải là unit test được, thì có lẽ nó phải là một phương thức public. Xem bài đăng này để có lời giải thích tốt cũng như stackoverflow.com/questions/5750279/…
mkelley33

2
Cũng cần lưu ý rằng bạn sẽ cần xác định các biến "riêng tư" của mình ở trên nơi chúng được sử dụng trong các hàm "công khai". Nếu không, CoffeeScript sẽ bị nhầm lẫn và tạo ra các varkhai báo nội bộ mới sẽ làm mờ chúng.
Andrew Miner

11

Tôi muốn thể hiện một cái gì đó thậm chí còn đẹp hơn

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

Bây giờ bạn có thể làm

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

Có một vấn đề với câu trả lời của Vitaly và đó là bạn không thể xác định các biến mà bạn muốn là duy nhất cho phạm vi, nếu bạn đặt tên riêng theo cách đó và sau đó thay đổi nó, giá trị tên sẽ thay đổi cho mọi phiên bản của lớp, vì vậy có một cách chúng ta có thể giải quyết vấn đề đó

# 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

Không thể truy cập vào mảng tên từ bên ngoài trừ khi bạn sử dụng getNames

Kiểm tra điều này

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 được biên dịch

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

Tôi thích cách triển khai này. Bất kỳ nhược điểm?
Erik5388

2

Đây là một giải pháp dựa trên một số câu trả lời khác ở đây cùng với https://stackoverflow.com/a/7579956/1484513 . Nó lưu trữ các biến cá thể riêng (không tĩnh) trong một mảng lớp riêng (tĩnh) và sử dụng ID đối tượng để biết phần tử nào của mảng đó chứa dữ liệu thuộc mỗi cá thể.

# 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

Dưới đây là bài viết tốt nhất mà tôi tìm thấy về cài đặt public static members, private static members, public and private members, và một số nội dung liên quan khác. Nó bao gồm nhiều chi tiết và jsso coffeesánh vs. Và vì những lý do lịch sử, đây là ví dụ mã tốt nhất từ ​​nó:

# 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

Đây là cách bạn có thể khai báo các thành viên riêng tư, không tĩnh trong Coffeescript
Để tham khảo đầy đủ, bạn có thể xem tại 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" trong kịch bản cà phê dẫn đến một kết quả dựa trên nguyên mẫu. Vì vậy, ngay cả khi bạn sử dụng một biến riêng, nó vẫn được chia sẻ giữa các phiên bản. Bạn có thể làm được việc này:

EventEmitter = ->
  privateName = ""

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

.. dẫn đến

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

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

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

Nhưng hãy cẩn thận đặt các thành viên private trước các hàm công cộng, bởi vì coffee script trả về các hàm công cộng dưới dạng đối tượng. Nhìn vào Javascript đã biên dịch:

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

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

0

Vì coffee script biên dịch thành JavaScript nên cách duy nhất bạn có thể có các biến riêng là thông qua các lệnh đóng.

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

Điều này sẽ được biên dịch lại thông qua JavaScript sau:

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

Tất nhiên điều này có tất cả các hạn chế giống như tất cả các biến riêng tư khác mà bạn có thể có thông qua việc sử dụng các bao đóng, ví dụ: các phương thức mới được thêm vào không có quyền truy cập vào chúng vì chúng không được xác định trong cùng một phạm vi.


9
Đó là loại thành viên tĩnh. e = new Animal(5);f = new Animal(1);e.test()cảnh báo một, tôi muốn năm.
thejh

@thejh Ồ, xin lỗi bạn, giờ tôi thấy lỗi, đoán là hôm qua đã quá muộn để nghĩ về chuyện này.
Ivo Wetzel

@thejh Điều đó đã xảy ra với tôi, tôi đã cố gắng giải quyết vấn đề đó trong câu trả lời của mình.
iConnor

0

Bạn không thể làm điều đó dễ dàng với các lớp CoffeeScript, vì chúng sử dụng mẫu phương thức khởi tạo Javascript để tạo các lớp.

Tuy nhiên, bạn có thể nói điều gì đó như sau:

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

Nhưng bạn mất đi sự tuyệt vời của các lớp CoffeeScript, bởi vì bạn không thể kế thừa từ một lớp được tạo theo cách đó bằng bất kỳ cách nào khác ngoài việc sử dụng lại extension (). instanceof sẽ ngừng hoạt động và các objec được tạo theo cách này sẽ tiêu tốn nhiều bộ nhớ hơn một chút. Ngoài ra, bạn không được sử dụng các từ khóa mớisiêu nữa.

Vấn đề là, các bao đóng phải được tạo mỗi khi một lớp được khởi tạo. Các đóng thành viên trong các lớp CoffeeScript thuần túy chỉ được tạo một lần - nghĩa là khi "kiểu" thời gian chạy của lớp được tạo.


-3

Nếu bạn chỉ muốn tách các thành viên riêng tư khỏi công khai, chỉ cần bọc nó trong $ biến

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

Và sử dụng @$.requirements

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.