Skip to content

Instantly share code, notes, and snippets.

@davepeck
Created December 7, 2012 21:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davepeck/4236746 to your computer and use it in GitHub Desktop.
Save davepeck/4236746 to your computer and use it in GitHub Desktop.
ES5 properties and coffee script
Function::property = (prop, desc) ->
Object.defineProperty this.prototype, prop, desc
class Foo
@property 'neat'
get: ->
42
class Bar extends Foo
@property 'neat'
get: ->
super
# 42
alert(new Foo().neat)
# exception
alert(new Bar().neat)
Function::property = (prop, desc) ->
Object.defineProperty this.prototype, prop, desc
class Foo
@property 'neat'
get: ->
42
class Bar extends Foo
@property 'neat'
get: ->
Bar.__super__.neat
# 42
alert(new Foo().neat)
# 42
alert(new Bar().neat)
@davepeck
Copy link
Author

davepeck commented Dec 7, 2012

Just something to watch out for if you like ES5 properties and coffeescript.

@davepeck
Copy link
Author

davepeck commented Dec 8, 2012

I also recommend the following if you're going this route:

# Will make IE7/8 happy -- although do we care?
if Object.prototype.__defineGetter and not Object.defineProperty
    Object.defineProperty = (obj, prop, desc) ->
        if "get" of desc
            obj.__defineGetter__ prop, desc.get
        if "set" of desc
            obj.__defineSetter__ prop, desc.set

@epidemian
Copy link

# Will make IE7/8 happy -- although do we care?

😄

If i had to guess, i'd say that the intersection between developers who want to use getters and setters in CoffeeScript and developers that care for IE7/8 compatibility is probably pretty small. But nice consideration nevertheless.

About the second example, although it's not broken like the first one, it won't work for the general case either: if the Foo's 'neat' property depended on something stored as an own property of the instance, calling .neat in the superclass prototype won't work. For example:

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

class Foo
  constructor: (@neatPrefix) ->

  @property 'neat'
    get: ->
      "#{@neatPrefix} 42"

class Bar extends Foo
  @property 'neat'
    get: ->
      Bar.__super__.neat

#42
alert (new Foo 'uber').neat # -> "uber 42"

#42
alert (new Bar 'uber').neat # -> "undefined 42"

The only way i found to make Bar's 'neat' work is to replace Foo.__super__.neat for (Object.getOwnPropertyDescriptor Bar.__super__, 'neat').get.apply @. Not very pretty 😅

I'm not sure trying to make inheritance and getters/setters work at the same time in CS over the current semantics of JS is very plausible. ECMAScript 6 adds a lot of classical OO to the mixture, so i'd expect that having a working super with getters and setters over that that instead would be much much easier.

@davepeck
Copy link
Author

The only way i found to make Bar's 'neat' work is to replace Foo.__super__.neat for (Object.getOwnPropertyDescriptor Bar.__super__, 'neat').get.apply @. Not very pretty

Yep, I ended up running into this too and settled on a similar pattern. It ain't pretty, at all. Alas.

@ben-x9
Copy link

ben-x9 commented Jul 25, 2015

How about this?

class Person
  constructor: (@firstName, @lastName) ->
  @get 'fullName', -> @firstName + ' ' + @lastName

class Captain extends Person
  @get 'fullName', -> 'Capt. ' + @getSuper 'fullName'

johnDoe = new Person 'John', 'Doe'
console.log johnDoe.fullName # -> "John Doe"

jackSparrow = new Captain 'Jack', 'Sparrow'
console.log jackSparrow.fullName # -> "Capt. Jack Sparrow"


# Setup

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

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

Object::getSuper = (prop) ->
  (Object.getOwnPropertyDescriptor @constructor.__super__, prop).get.apply @

Object::setSuper = (prop) ->
  (Object.getOwnPropertyDescriptor @constructor.__super__, prop).set.apply @

@ben-x9
Copy link

ben-x9 commented Jul 25, 2015

Better setup (non-enumerable)

Object.defineProperty Function.prototype, 'get', value: (prop, get) ->
  Object.defineProperty @prototype, prop, {get}

Object.defineProperty Function.prototype, 'set', value: (prop, set) ->
  Object.defineProperty @prototype, prop, {set}

Object.defineProperty Object.prototype, 'getSuper', value: (prop) ->
  (Object.getOwnPropertyDescriptor @constructor.__super__, prop).get.apply @

Object.defineProperty Object.prototype, 'setSuper', value: (prop) ->
  (Object.getOwnPropertyDescriptor @constructor.__super__, prop).set.apply @

@ben-x9
Copy link

ben-x9 commented Jul 25, 2015

Better setup (non-enumerable)

Object.defineProperty Function.prototype, 'get',
  configurable: yes
  writable: yes
  value: (prop, get) ->
    Object.defineProperty @prototype, prop, {get}

Object.defineProperty Function.prototype, 'set',
  configurable: yes
  writable: yes
  value: (prop, set) ->
    Object.defineProperty @prototype, prop, {set}

Object.defineProperty Object.prototype, 'getSuper',
  configurable: yes
  writable: yes
  value: (prop) ->
    (Object.getOwnPropertyDescriptor @constructor.__super__, prop).get.apply @

Object.defineProperty Object.prototype, 'setSuper',
  configurable: yes
  writable: yes
  value: (prop) ->
    (Object.getOwnPropertyDescriptor @constructor.__super__, prop).set.apply @

@Asc2011
Copy link

Asc2011 commented Jul 27, 2016

Hi all and thx for the example(s),

@ben-x9:
It works with setters when one changes the getter-definition

Object.defineProperty Function.prototype, 'get',
  configurable: yes
  writable: yes
  value: (prop, get) ->
    Object.defineProperty @prototype, prop, {
     get: get
     configurable: yes
     writable: yes
   }

to allow a following setter-definition. The code as-is throws 'Cannot redefine property ' in Chrome.

cheers, Andreas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment