web-dev-qa-db-fra.com

CoffeeScript: Getter / Setter dans les initialiseurs d'objets

ECMAScript nous permet de définir les getters ou setters comme suit:

[texte/javascript]

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

Je peux contourner le problème si j'utilise une classe :

[texte/coffeescript]

"use strict"

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

class Class
      property: ''

      @trigger 'getable', ->
               'x'

      member: 0

Mais que se passe-t-il si je veux définir le déclencheur sur l'objet directement - sans en utilisant defineProperty/-ies. Je veux faire quelque chose comme (ça ne fonctionne pas de cette façon):

[texte/x-pseudo-coffeescript]

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

Cela fonctionne en JavaScript sans aucun problème et je ne veux pas que mes scripts régressent lorsque j'utilise CoffeeScript. N'y a-t-il pas un moyen de faire cela aussi confortable qu'en JavaScript/ECMAScript? Merci.

42
fridojet

Non, pas pour l'instant :(

Depuis FAQ CoffeeScript :

Q: Allez-vous ajouter la fonctionnalité X lorsque la fonctionnalité X dépend d'une plate-forme?

R: Non, les fonctionnalités spécifiques à l'implémentation ne sont pas autorisées en tant que stratégie. Tout ce que vous écrivez en CoffeeScript doit être pris en charge et exécutable sur n'importe quelle implémentation JavaScript actuelle (en pratique, cela signifie que le plus petit dénominateur commun est IE6). Ainsi, des fonctionnalités telles que les suivantes ne seront pas implémentées: getters & setters, yield.

Quelques problèmes avec GitHub à propos de la syntaxe getter et setter: # 64 , # 451 , # 1165 (il y a une discussion intéressante dans la dernière).

Personnellement, je pense qu'avoir une syntaxe littérale getter et setter serait une fonctionnalité intéressante pour CoffeeScript maintenant que defineProperty est partie de la norme ECMAScript . Le besoin de getters & setters en JavaScript peut être discutable, mais vous n'êtes pas obligé de les utiliser simplement parce qu'ils existent.


Quoi qu'il en soit, comme vous l'avez remarqué, il n'est pas si difficile d'implémenter une fonction wrapper pratique qui appelle Object.defineProperty pour les déclarations de classe. Personnellement, j'utiliserais l'approche suggérée dans ici :

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

Ou, créez peut-être deux méthodes différentes:

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

Pour les objets simples, vous pouvez simplement utiliser Object.defineProperty (ou Object.defineProperties ;)) sur l'objet lui-même comme Jason a proposé . Peut-être envelopper cela dans une petite fonction:

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
76
epidemian

Voici une autre approche pour définir des propriétés avec des getters et setters dans CoffeeScript qui maintient une syntaxe relativement propre sans rien ajouter au prototype global Function (ce que je préfère ne pas faire):

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

Il fonctionne bien avec de nombreuses propriétés. Par exemple, voici une classe Rectangle qui est définie en termes de (x, y, largeur, hauteur), mais fournit des accesseurs pour une autre représentation (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

Voici le code JavaScript correspondant . Prendre plaisir!

30
curran

Vous pouvez également utiliser Object.defineProperty sur des objets JSON droits.

obj = {}
Object.defineProperty obj, 'foo',
    get: ->
        return 'bar'

La notation get/set ne fonctionne pas pour diverses raisons dans CoffeeScript. Le plus grand étant que le compilateur n'a pas été construit pour prendre en compte la notation get/set.

Notez que get/set n'est pas pris en charge par tous les navigateurs (en particulier IE). Notez également que les nouvelles normes ECMA (ECMAScript5) mentionnent Object.defineProperty comme le moyen de définir des propriétés avec des getters/setters.

8
Jason L.

Comme @curran, je préfère ne pas modifier le prototype Function. Voici ce que j'ai fait dans l'un de mes projets:

Définissez quelque part une fonction utilitaire qui pour une classe donnée renvoie 2 fonctions vous permettant d'ajouter facilement des getters et setters sur le prototype de la classe:

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

gs signifie g etter et s etter.

Ensuite, vous créez et importez les deux fonctions configurées pour votre classe:

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
5
Mickaël Gauvin

Merci aux autres qui l'ont précédé. Très généralement et simplement:

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"
1
kwerle

Une approche alternative:

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
M K