可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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
回答2:
Here's another approach for defining properties with getters and setters in CoffeeScript that maintains a relatively clean syntax without adding anything to the global Function prototype (which I'd rather not do):
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
It works well with many properties. For example, here's a Rectangle class that is defined in terms of (x, y, width, height), but provides accessors for an alternative representation (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
Here's the corresponding JavaScript code. Enjoy!
回答3:
You can use Object.defineProperty on straight JSON objects as well.
obj = {}
Object.defineProperty obj, 'foo',
get: ->
return 'bar'
The get/set notation does not work for various reasons in CoffeeScript. The biggest being that the compiler has not been built to account for get/set notation.
Note that get/set is not supported by all browsers (specifically, IE). Also note that the new ECMA standards (ECMAScript5) mentions Object.defineProperty as the way to define properties with getters/setters.
回答4:
Like @curran, I prefer not to modify Function
prototype.
Here it is what I did in one of my projects :
Define somewhere an utility function which for a given class returns 2 functions allowing you to easily add getters and setters on the prototype of the class:
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 stands for getter and setter.
Then, you build and import the two functions configured for your class :
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:
An alternative approach:
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
回答6:
Thanks to the others that have gone before. Very generally and simply:
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"