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