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

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

[текст/javascript]

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

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

[текст/кофескрипт]

"use strict"

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

class Class
      property: ''

      @trigger 'getable', ->
               'x'

      member: 0

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

[текст/x-псевдо-кофескрипт]

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

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


person fridojet    schedule 20.07.2012    source источник
comment
Пока нет :(. Цитата из FAQ: Q : Будете ли вы добавлять функцию X там, где функция X зависит от платформы? О: Нет, функции, зависящие от реализации, не допускаются в качестве политики. Все, что вы пишете на CoffeeScript, должно поддерживаться и запускаться в любой текущей реализации JavaScript (на практике это означает, что наименьший общий знаменатель - IE6). Таким образом, такие функции, как следующие, не будут реализованы: геттеры и сеттеры, yield. Я думаю, что литеральный синтаксис геттеров и сеттеров был бы хорошей опцией для CoffeeScript. .   -  person epidemian    schedule 21.07.2012
comment
@epidemian Спасибо, это то, что я хотел знать. - Но: возможно ли создать такую ​​функцию подписки для CoffeeScript чисто (без прямого изменения компилятора)?   -  person fridojet    schedule 21.07.2012
comment
Я не думаю, что это было бы возможно. Существует ветвь, которая их реализует, но она не поддерживается для кажется, долгое время (и я бы не рекомендовал использовать форк CoffeeScript только для этой функции). Я добавил ответ с моим предыдущим комментарием и немного дополнительной информации.   -  person epidemian    schedule 21.07.2012
comment
Ошибки: я не думаю, что это возможно :P   -  person epidemian    schedule 21.07.2012


Ответы (6)


Нет, пока нет :(

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

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

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

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

Лично я считаю, что наличие литерального синтаксиса getter & setter было бы неплохой возможностью для 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
person epidemian    schedule 21.07.2012
comment
Здесь есть ошибка в производных классах, использующих свойства. super ведет себя не так, как вы могли бы ожидать. Ничего страшного, но стоит отметить. См. gist.github.com/4236746. - person Dave Peck; 08.12.2012
comment
@DavePeck Хороший улов! Спасибо, что указали на это. Скомпилированный вывод первого примера в вашем Gist очень испорчен. Кажется, в целом поведение super довольно хрупкое, я удивлен, что даже obj = foo: -> super компилируется (во что-то сломанное). К счастью, семантика super скорее всего будет пересмотрена для CoffeeScript 2. Однако я сомневаюсь, что будет какой-либо план сделать super дружественным к геттеру/сеттеру. - person epidemian; 08.12.2012

Вот еще один подход к определению свойств с помощью геттеров и сеттеров в 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. Наслаждаться!

person curran    schedule 19.03.2013
comment
Это круто! Оно работает!! Я люблю CoffeeScript, но это ГЛУПЫЕ геттеры/сеттеры изначально не поддерживаются. - person Pete Alvin; 27.02.2015
comment
FWIW, синтаксис @prototype, похоже, у меня не работает (в конструкторе или вне его). Я не уверен, почему - другие примеры также предполагают этот формат. Но в случае, если кто-то еще столкнется с этой проблемой, простое выполнение Object.defineProperties Rectangle... (вне области объявления класса), кажется, работает надежно для меня. - person Rod; 16.12.2020

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

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

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

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

person Jason L.    schedule 20.07.2012
comment
Хорошо, но есть ли способ расширить CoffeeScript таким образом чисто? - person fridojet; 21.07.2012
comment
Чтобы разрешить получение myGetter.. и установку mySetter...? Нет, не без входа в компилятор и внесения этих изменений. - person Jason L.; 21.07.2012
comment
Я не думаю, что из-за того, что это язык с важными пробелами, а компилятор не был создан для учета нотации get/set, это причина, по которой кофескрипт не поддерживает его. Вы подразумеваете, что отсутствие пробелов - это проблема, cs не могла поддерживать синтаксис, где действительная причина вместо этого заключается в том, что функции, специфичные для реализации, не разрешены в качестве политики. - person Angelos Pikoulas; 11.11.2012
comment
@Agelos, ты прав. Почему-то первое, о чем я подумал, отвечая на этот вопрос, это запутает компилятор!. Ваша точка зрения более точна :) - person Jason L.; 28.11.2012

Как и @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 означает getter и setter.

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

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
person Mickaël Gauvin    schedule 10.03.2015

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

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
person M K    schedule 21.11.2016

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

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"
person kwerle    schedule 16.03.2017