![](https://habrastorage.org/files/de5/e4a/985/de5e4a98504441f497809e035f923d93.jpg)
За время (уже более двух лет), проведённое за штурвалом этого препроцессора, накопилось довольно много «хотелок», которые желалось бы увидеть в JS (благо есть опыт общения с другими языками), некоторые из которых мне удалось претворить в жизнь, местами коряво, но как есть — CoffeeScript позволяет почти что придумывать свои конструкции. Об этих «хотелках» я и хочу поведать в статье, прошу под кат.
Пространства имён
Первое что хотелось бы видеть — это пространства имён. Какому разработчику не хочется видеть аккуратно расположенный код не только в файловой системе, но и в иерархии классов? Гугление подсказало несколько решений, и самым элегантным было использование литералов объектов в качестве имён пространств:
namespace MyApplication:Some:
class Any
# Этот класс будет располагаться в window.MyApplication.Some.Any
namespace global:
class Some
# Этот класс будет лежать в window.Some
Происходит следующее: Мы вызываем функцию
namespace
и отправляем туда объект {MyApplication:{Some: сам_класс}}
Единственное «но» — подобное решение обладало некоторыми проблемами, а именно — требовалось строгая последовательность подключения файлов (вначале с пространством MyApplication, затем с MyApplication.Some и т.д.), а так же хардкор регулярками с получением имени класса. Я постарался избавиться от их фатальных недостатков, и в результате получился такой код:
window.namespace = ->
args = arguments[0]
target = global || window
loop
for subpackage, obj of args
target = target[subpackage] or= {}
args = obj
break unless typeof args is 'object'
Class = args
target = window if arguments[0].hasOwnProperty 'global'
name = if !Class.name? # IE Fix
args.toString().match(/^function\s(\w+)\(/)[1]
else
Class.name
proto = target[name] or undefined
target[name] = Class
if proto?
for i of proto
target[name][i] = proto[i]
Код уже проверенный на боевых проектах, не тормозит особо и ничего, кроме огромной кучи удовольствия, не принёс.
Импорт классов\функций
Конечно же, хотелось бы наличия операторов, вроде
using
, use
, import
, но увы — реализовать подобные можно лишь на уровне самого препроцессора, но никак не на уровне языка. Но в случае CoffeeScript, оказывается, есть некоторые свойства самого языка, позволяющие реализовать импорт почти что красиво:{Any} = MyApplication.Some # Импортировать MyApplication.Some.Any под именем Any
{Any: Test} = MyApplication.Some # Импортировать MyApplication.Some.Any под именем Test
эти операции аналогичны, допустим
use
в php:use MyApplication\Some\Any;
use MyApplication\Some\Any as Test;
Подобное поведение конечно же задокументировано (пункт: «Destructuring Assignment» пример №3 в офф. документации), но, если честно, я очень сильно удивился, когда заметил подобную конструкцию в чьём-то коде. Когда читал документацию — просто не заметил этого.
Константы
Есть несколько популярных вариантов реализации констант в CoffeeScript.
Вариант 1
class Some
@MY_DEFINE = 42
@getVar: ->
@MY_DEFINE
getVar: ->
@contructor.MY_DEFINE # или Some.MY_DEFINE
В таком варианте есть как плюсы, так и минусы:
+ Константу (теоретическую, т.к. это просто переменная) можно получить из любого места, обратившись к
Some.MY_DEFINE
— Использовать не всегда удобно
— Это переменная (т.е. можно перезаписать), а использование
__defineGetter__
и аналогичных конструкций создания геттеров — только усложнит чтение.Вариант номер два
class Some
MY_DEFINE = 42
@getVar: ->
MY_DEFINE
getVar: ->
MY_DEFINE
плюсы и минусы:
+ Внутри класса выглядит великолепно, читаемо и пользоваться очень удобно
— Одноразовая, т.к. ограничена только текущей (и вложенными) областью видимости, за пределами класса получить её значение невозможно
— Невозможно реализовать геттеры\сеттеры, чтобы оградить значение от изменений
Есть ещё варианты, вроде использования джаваскриптовых
const MY_DEFINE = 42
в обратных одинарных кавычках (там где буква «Ё»), или добавления функций в прототип Function
, которые будут регистрировать константы с помощью геттеров\сеттеров, но это малопопулярные техники, так что я о них промолчу и лучше предложу свой вариант (чуть более приближенный к реальности):Третий вариант
class Ajax
define AJAX_UNSENT: 0
define AJAX_OPENED: 1
define AJAX_HEADERS_RECEIVED: 2
define AJAX_LOADING: 3
define AJAX_READY: 4
request: ->
# некоторый код
if xhr.status is AJAX_READY
# делать что-нибудь
Реализация самой функции:
window.define = (args) ->
for name, val of args
continue if window[name]?
do (name, val) ->
unless window.__defineGetter__? # IE Fix
return window[name] = val
window.__defineGetter__ name, -> val
window.__defineSetter__ name, -> throw new Error "Can not redeclare define #{name}. Define already exists."
# Ну и можно добавить функцию проверки на существование
window.defined = (name) ->
return window.__lookupGetter__(name)? && window[name]?
Происходит следующее: Вызываем функцию, куда передаём объект. Далее мы в
window
регистрируем геттер, который будет возвращать нужное значение и сеттер, который блокирует возможность перезаписи константы.Плюсы:
+ Внутри классов также выглядит очень красиво и читаемо
+ Можно получить в любом месте кода
— Висит в
window
— глобалсы никогда не были хорошим решением, но я не думаю, что это так уж существенно в свете возможных выигрышей по читаемости и удобству кода, а проблемы коллизий решаются обычными префиксами.Приватные переменные
Этот пример только в качестве бонуса и просто как идея. Мне самому подобная реализация не особо нравится, но пока лучшего я придумать не смог:
class Some
test = $private 'test'
constructor: ->
@[test] = 23
console.log(new Some)
Что же тут происходит: Внутри класса мы объявляем
var
переменную test
, в качестве значения которой будет строка [private test]
(её возвращает функция $private). Далее мы просто используем эту переменную как имя для нашей реальной переменной. А так как имя у нас начинается с невидимого символа — доступ к переменной получить довольно сложно, особенно если префикс будет генерироваться из случайных невидимых символов.Реализация:
window.$private = (name) ->
unless defined 'ZERO_WIDTH_SPACE'
define ZERO_WIDTH_SPACE: '' # Тут пробел с нулевой шириной, в качестве значения
return "#{ZERO_WIDTH_SPACE}[private #{name}]"
В результате:
+ Реальные приватные переменные
+ В классах довольно объёмного размера может очень сильно помочь, т.к. очищает интерфейс этого класса от лишних методов\свойств, которые не стоит делать публичными
— Очень громоздко и некрасиво
— Неудобно пользоваться
— Приходится добавлять префикс "$", т.к. это ключевое слово и оно зарезервировано
Имена классов
Иногда хочется получить имя класса или различать массив и объект. Такие ситуации встречаются довольно часто, и для таких случаев я припрятал для себя небольшую функцию:
nameof [] # 'Array'
nameof {} # 'Object'
nameof SomeClass # 'SomeClass'
Сама реализация выглядит вот так:
window.nameof = (cls) ->
if typeof cls is 'object'
cls = cls.constructor
else if typeof cls isnt 'function'
return typeof cls
name = unless cls.name?
cls.toString().match(/function\s(\w+)\(/)[1]
else
cls.name
return name
Абстрактные методы
Наверное, самое элегантное и простое решение:
class Some
abstractMethod: abstract
class Any extends Some
(new Any).abstractMethod() # Error 'Can not call abstract method'
# Но зато
class Any2 extends Some
abstractMethod: -> console.log 42
(new Any).abstractMethod() # 42
Реализация элементарнейшая и очевиднейшая:
window.abstract = -> throw new Error 'Can not call abstract method'
Эпилог
Я привёл несколько интересных примеров, как можно улучшить читаемость (по моему мнению) и удобство использования кода парой-тройкой небольших функций. Некоторые из них вполне могут сгодиться для организации серьёзного кода, некоторые, как небольшие хелперы, но в целом они выполняют одну роль — добавляют некоторые языковые конструкции в сам язык. Именно по этому я и хочу предостеречь тебя, читатель. Может быть всё это хорошо и красиво, и даже на практике удобно, но сахар — хорош в меру, не стоит сильно злоупотреблять подобными безумными функциями.
P.S. Прошу прощения за хаб «JavaScript», увы, по CoffeeScript отсутствует.
UPD: Добавил откат констант для ИЕ, от греха подальше =)
Only registered users can participate in poll. Log in, please.
Что-нибудь из этого было полезно?
31.31% Пространства имён62
20.71% Импорт пространств41
11.11% Константы22
9.09% Приватные переменные18
12.12% Имена классов24
14.14% Абстрактные методы28
20.71% Это всё от лукавого, наоборот только путает41
48.99% Не использую CoffeeScript97
198 users voted. 109 users abstained.