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