Search code examples
javascriptoopcoffeescriptgetter-setterecmascript-5

CoffeeScript: Getter/Setter in Object Initializers


ECMAScript allows us to define getters or setters as following:

[text/javascript]

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

I can work around if I'm using a class:

[text/coffeescript]

"use strict"

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

class Class
      property: ''

      @trigger 'getable', ->
               'x'

      member: 0

But what if I want to define trigger on the object directly - without using defineProperty / -ies. I want to do something like (it's not working that way):

[text/x-pseudo-coffeescript]

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

It's working in JavaScript without any problems and I don't want my scripts to regress when I'm using CoffeeScript. Isn't there a way to do this as comfortable as in JavaScript/ECMAScript? Thanks.


Solution

  • No, not for now :(

    From the CoffeeScript FAQ:

    Q: Will you add feature X where feature X depends on a platform?

    A: No, implementation-specific features are not allowed as a policy. Everything that you write in CoffeeScript should be supported and runnable on any current JavaScript implementation (in practice, this means the lowest common denominator is IE6). Thus, features such as the following will not be implemented: getters & setters, yield.

    Some GitHub issues about getter & setter syntax: #64, #451, #1165 (there is some nice discussion in the last one).

    I personally think that having getter & setter literal syntax would be a nice opt-in feature for CoffeeScript now that defineProperty is part of the ECMAScript standard. The need for getters & setters in JavaScript can be questionable, but you're not forced to use them just because they exist.


    Anyway, as you noticed, it's not that hard to implement a convenient wrapper function that calls Object.defineProperty for class declarations. I personally would use the approach suggested in here:

    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
    

    Or, maybe create two different methods:

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

    For plain objects you can just use Object.defineProperty (or Object.defineProperties ;) ) on the object itself as Jason proposed. Maybe wrap that in a little function:

    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