Подтвердить что ты не робот

CoffeeScript: Getter/Setter в инициализаторах объектов

ECMAScript позволяет определить геттеры или сеттеры следующим образом:

[текст/JavaScript]

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

Я могу работать, если я использую класс:

[текст/CoffeeScript]

"use strict"

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

class Class
      property: ''

      @trigger 'getable', ->
               'x'

      member: 0

Но что, если я хочу определить триггер для объекта напрямую - без, используя defineProperty/- ies. Я хочу сделать что-то вроде (это не работает):

[текст/х-псевдо-CoffeeScript]

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

Он работает в JavaScript без каких-либо проблем, и я не хочу, чтобы мои скрипты регрессировали, когда я использую CoffeeScript. Нет ли способа сделать это так же комфортно, как в JavaScript/ECMAScript? Спасибо.

4b9b3361

Ответ 1

Нет, не сейчас: (

Из Часто задаваемые вопросы о CoffeeScript:

Q: Вы добавите функцию X, где функция X зависит от платформы?

A: Нет, специфические для реализации функции не допускаются как политика. Все, что вы пишете в CoffeeScript, должно поддерживаться и выполняться при любой текущей реализации JavaScript (на практике это означает, что самым низким общим знаменателем является IE6). Таким образом, такие функции, как следующие, не будут реализованы: getters и seters, yield.

Некоторые проблемы GitHub о синтаксисе getter и setter: # 64, # 451, # 1165 (в последнем есть приятное обсуждение).

Я лично считаю, что наличие синтаксиса getter и setter literal было бы хорошей функцией выбора для CoffeeScript теперь, когда defineProperty часть стандарта ECMAScript. Потребность в геттерах и сеттерах в JavaScript может быть сомнительной, но вы не вынуждены использовать их только потому, что они существуют.


В любом случае, как вы заметили, не так сложно реализовать удобную функцию-оболочку, которая вызывает Object.defineProperty для деклараций классов. Я лично использовал бы подход, предложенный в здесь:

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

Или, возможно, создайте два разных метода:

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

Для простых объектов вы можете просто использовать Object.defineProperty (или Object.defineProperties;)) для самого объекта как предложил Джейсон. Возможно, оберните это небольшой функцией:

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

Вот еще один подход для определения свойств с геттерами и сеттерами в CoffeeScript, который поддерживает относительно чистый синтаксис, не добавляя ничего в глобальный прототип функции (чего я бы предпочел не делать):

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

Он хорошо работает со многими свойствами. Например, здесь класс Rectangle, который определяется в терминах (x, y, width, height), но предоставляет аксессуры для альтернативного представления (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

Здесь соответствующий код JavaScript. Наслаждайтесь!

Ответ 3

Вы можете использовать Object.defineProperty для прямых объектов JSON.

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

Обозначение get/set не работает по различным причинам в CoffeeScript. Самое большое существо, что компилятор не был создан для учета ввода/установки.

Обратите внимание, что get/set не поддерживается всеми браузерами (в частности, IE). Также обратите внимание, что в новых стандартах ECMA (ECMAScript5) упоминается Object.defineProperty как способ определения свойств с помощью getters/seters.

Ответ 4

Как @curran, я предпочитаю не модифицировать прототип Function. Вот что я сделал в одном из моих проектов:

Определите где-нибудь функцию утилиты, которая для данного класса возвращает 2 функции, позволяющие легко добавлять геттеры и сеттеры в прототипе класса:

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 обозначает g etter и s.

Затем вы создаете и импортируете две функции, настроенные для вашего класса:

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

Альтернативный подход:

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

Спасибо другим, которые ушли раньше. Очень просто и просто:

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"