CoffeeScript:对象初始化中的Getter/Setter

43

ECMAScript允许我们按照以下方式定义getter或setter:

[text/javascript]

var object = {
  property: 7,
  get getable() { return this.property + 1; },
  set setable(x) { this.property = x / 2; }
};

如果我使用一个 class,我可以解决这个问题:

[text/coffeescript]

"use strict"

Function::trigger = (prop, getter, setter) ->
      Object.defineProperty @::,
              get: getter
              set: setter               

class Class
      property: ''

      @trigger 'getable', ->
               'x'

      member: 0

但是如果我想在对象上直接定义触发器,而不使用defineProperty / -ies,该怎么办呢?我想做这样的事情(以这种方式不起作用):

[text/x-pseudo-coffeescript]

object =
  property: 'xhr'
  get getable: 'x'

在JavaScript中,它运行良好,我不希望在使用CoffeeScript时我的脚本出现问题。难道没有一种方法可以使它像JavaScript/ECMAScript那样容易吗?谢谢。


3
暂时不行 :(. 这是常见问题解答中的一句话: "问:您是否会添加依赖于平台的功能X?答:不,作为政策,不允许使用特定于实现的功能。 在CoffeeScript中编写的所有内容都应该在任何当前JavaScript实现上得到支持和运行(实际上,这意味着最低公共分母是IE6)。 因此,不会实现以下功能:getter和setter,yield_等功能。" 我认为在CoffeeScript中增加getter和setter文字语法将是一项不错的选择性功能。 - epidemian
@epidemian 谢谢,这就是我想知道的。但是:是否有可能为CoffeeScript构建这样的选择加入功能(而不直接修改编译器)? - fridojet
我认为那不可能实现。有一个分支实现了它们,但好像已经很长时间没有维护了(而且我不建议仅因为这个特性使用 CoffeeScript 分支)。我在回答中添加了我的先前评论和一些更多的信息。 - epidemian
勘误:我不认为那是可能的:P - epidemian
6个回答

78

不,暂时不会 :(

CoffeeScript FAQ中得知:

问: 你会加入依赖于平台的功能X吗?

答: 不会,根据政策,不允许使用特定于实现的特性。你在CoffeeScript中编写的所有内容都应该在任何当前JavaScript实现上支持和运行(实际上,这意味着最低公共分母是IE6)。因此,以下功能将不会被实现: getters & setters, yield.

一些关于getter & setter语法的GitHub问题: #64, #451, #1165 (最后一个问题有一些好的讨论)。

我个人认为,现在 defineProperty成为ECMAScript标准的一部分,为CoffeeScript添加getter和setter字面语法将是一个不错的可选功能。在JavaScript中需要使用getter和setter有时可能会引起质疑,但你并不强制使用它们只是因为它们存在。


无论如何,正如你所注意到的那样,实现一个方便的包装函数来调用Object.defineProperty以进行类声明并不那么困难。我个人会使用这里建议的方法:

Function::property = (prop, desc) ->
  Object.defineProperty @prototype, prop, desc

class Person
  constructor: (@firstName, @lastName) ->
  @property 'fullName',
    get: -> "#{@firstName} #{@lastName}"
    set: (name) -> [@firstName, @lastName] = name.split ' '

p = new Person 'Robert', 'Paulson'
console.log p.fullName # Robert Paulson
p.fullName = 'Space Monkey'
console.log p.lastName # Monkey

或者,也许创建两种不同的方法:

Function::getter = (prop, get) ->
  Object.defineProperty @prototype, prop, {get, configurable: yes}

Function::setter = (prop, set) ->
  Object.defineProperty @prototype, prop, {set, configurable: yes}

class Person
  constructor: (@firstName, @lastName) ->
  @getter 'fullName', -> "#{@firstName} #{@lastName}"
  @setter 'fullName', (name) -> [@firstName, @lastName] = name.split ' '

对于普通对象,你可以直接在对象本身上使用Object.defineProperty(或Object.defineProperties ;)),就像Jason提议的那样。也许可以将其包装在一个小函数中:

objectWithProperties = (obj) ->
  if obj.properties
    Object.defineProperties obj, obj.properties
    delete obj.properties
  obj

rectangle = objectWithProperties
  width: 4
  height: 3
  properties:
    area:
      get: -> @width * @height

console.log rectangle.area # 12
rectangle.width = 5
console.log rectangle.area # 15

3
在派生类中使用属性时,有一个需要注意的地方。super的行为可能不如您所预期。虽然并不是很重要,但值得指出。请参见https://gist.github.com/4236746。 - Dave Peck
@DavePeck 很好的发现!感谢你指出来。你的Gist中第一个示例的编译输出非常混乱。似乎super的行为相当脆弱,我很惊讶甚至obj = foo: -> super 编译(变成了一些错误的东西)。幸运的是,super的语义可能会被修订,用于CoffeeScript 2。然而,我怀疑不会有任何计划使super成为getter/setter友好型。 - epidemian

33

这里有另一种使用CoffeeScript定义具有getter和setter属性的方法,保持相对干净的语法而不向全局Function原型添加任何内容(我宁愿不这样做):

class Person
  constructor: (@firstName, @lastName) ->
  Object.defineProperties @prototype,
    fullName:
      get: -> "#{@firstName} #{@lastName}"
      set: (name) -> [@firstName, @lastName] = name.split ' '

p = new Person 'Robert', 'Paulson'
console.log p.fullName # Robert Paulson
p.fullName = 'Space Monkey'
console.log p.lastName # Monkey

它适用于许多属性。例如,这里有一个矩形类,它是根据(x,y,width,height)定义的,但提供了另一种表示方式(x1,y1,x2,y2)的访问器:

class Rectangle                                     
  constructor: (@x, @y, @w, @h) ->
  Object.defineProperties @prototype,
    x1:
      get: -> @x
      set: (@x) ->
    x2:
      get: -> @x + @w
      set: (x2) -> @w = x2 - @x
    y1:
      get: -> @y
      set: (@y) ->
    y2:
      get: -> @y + @h
      set: (y2) -> @w = y2 - @y

r = new Rectangle 5, 6, 10, 11
console.log r.x2 # 15

这里是相应的JavaScript代码。享受吧!


2
这太棒了!它能用!!我喜欢CoffeeScript,但它愚蠢的getter/setter没有本地支持。 - Pete Alvin
就我个人而言,“@prototype”语法对我来说似乎无法正常工作(无论是在构造函数内部还是外部)。我不确定为什么——其他示例也建议使用该格式。但是,如果有人遇到这个问题,只需在类声明的范围之外执行“Object.defineProperties Rectangle...”即可可靠地解决问题。 - Rod

8
你可以在普通的JSON对象上使用Object.defineProperty。
obj = {}
Object.defineProperty obj, 'foo',
    get: ->
        return 'bar'

在CoffeeScript中,get/set表示法由于种种原因无法使用。最大的原因是编译器没有考虑到get/set表示法。

请注意,并非所有浏览器都支持get/set(特别是IE)。还要注意,新的ECMA标准(ECMAScript5)将Object.defineProperty作为定义具有getter/setter的属性的方法。


好的,但是有没有一种干净的方法来扩展 CoffeeScript? - fridojet
允许使用get myGetter...和set mySetter...吗?不行,除非进入编译器并进行更改。 - Jason L.
我认为CoffeeScript不支持该语法的原因并不是因为它是一种“空格敏感的语言”,也不是因为编译器没有考虑到get/set符号表示法。你在暗示“空格性”是导致cs无法支持该语法的问题,而实际上,合理的原因是“不允许特定于实现的功能作为政策”。 - Angelos Pikoulas
@Agelos 你说得对。不知为何,当我回答这个问题时,我首先想到的是“那会让编译器困惑!”你的观点更准确 :) - Jason L.

5

跟@curran一样,我不喜欢修改Function原型。在我的其中一个项目中,我做的是:

定义一个实用函数,在给定类的情况下返回2个函数,允许您轻松地在该类的原型上添加getter和setter:

gs = (obj) ->
  getter: (propName, getterFunction) ->
    Object.defineProperty obj.prototype, propName, 
      get: getterFunction
      configurable: true
      enumerable: true
  setter: (propName, setterFunction) ->
    Object.defineProperty obj.prototype, propName, 
      set: setterFunction
      configurable: true
      enumerable: true
gsgetter(获取器)和setter(设置器)的缩写。
然后,您需要构建并导入两个配置为您的类服务的函数:
class Dog
  { getter, setter } = gs @

  constructor: (name, age) -> 
    @_name = name
    @_age = age

  getter 'name', -> @_name
  setter 'name', (name) -> 
    @_name = name
    return

  getter 'age', -> @_age
  setter 'age', (age) -> 
    @_age = age
    return

1
一种替代方案:

get = (self, name, getter) ->
  Object.defineProperty self, name, {get: getter}

set = (self, name, setter) ->
  Object.defineProperty self, name, {set: setter}

prop = (self, name, {get, set}) ->
  Object.defineProperty self, name, {get: get, set: set}

class Demo 
  constructor: (val1, val2, val3) ->
    # getter only
    get @, 'val1', -> val1
    # setter only
    set @, 'val2', (val) -> val2 = val
    # getter and setter
    prop @, 'val3', 
      get: -> val3
      set: (val) -> val3 = val

1
感谢之前的人们。非常普遍和简单:
attribute = (self, name, getterSetterHash) ->
  Object.defineProperty self, name, getterSetterHash

class MyClass
  constructor: () ->
    attribute @, 'foo',
      get: -> @_foo ||= 'Foo' # Set the default value
      set: (who) -> @_foo = "Foo #{who}"

    attribute @, 'bar',
      get: -> @_bar ||= 'Bar'

    attribute @, 'baz',
      set: (who) -> @_baz = who


myClass = new MyClass()
alert(myClass.foo) # alerts "Foo"
myClass.foo = 'me' # uses the foo setter
alert(myClass.foo) # alerts "Foo me"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接