CoffeeScript: Getter/Setter in Object Initializers

fridojet picture fridojet · Jul 20, 2012 · Viewed 20.6k times · Source

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.

Answer

epidemian picture epidemian · Jul 21, 2012

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