Перед тем, как прямиком перейти к основной теме этого раздела, я бы хотел повторить: несмотря на то, что синтаксис CoffeeScript похож на синтаксис JavaScript, CoffeeScript не является подмножеством JavaScript. Поэтому нельзя писать JS-конструкции в CoffeeScript-файле, так как при компиляции высветится ошибка. Если вы пишете код на CoffeeScript, то он должен быть написан на чистом CoffeeScript. Не надо смешивать два разных языка.
Почему CoffeeScript не является подмножеством JavaScript? Потому что CoffeeScript уделяет весьма значительное внимание чистоте и лаконичности кода. В CoffeeScript при сохранении общей функциональности многие ключевые слова, существующие в JavaScript, не используются, благодаря чему сократилось количество ошибок и кода.
Кстати, я нахожу весьма странным то, что компилятор CoffeeScript написан на CoffeeScript! Похоже, что на вопрос «что было раньше — курица или яйцо» наконец-то найден ответ!
Итак, давайте рассмотрим основы. В CoffeeScript нет нужды добавлять точку с запятой в конце строки — компилятор автоматически её добавит при компиляции. Точки с запятой являются причиной многочисленных дискуссий в JavaScript сообществе, в частности, из-за странного поведения интерпретатора при их обработке. Слава богу, CoffeeScript решает эту проблему просто убрав точки с запятой из своего синтаксиса.
Комментарии форматируются так же, как и в языке Ruby, начинаясь со знака #
.
# A comment
Мультистрочные комментарии также поддерживаются. Они начинаются и заканчиваются тремя символами #
.
###
Комментарий, который занимает
несколько строк.
###
Как я уже упомянул ранее, CoffeeScript весьма лаконичен. На практике это означает к примеру то, что вы можете заменить фигурные скобки ({}) tab'ом. Эта фича вдохновлена языком Python. Есть и недостатки — придётся внимательно следить за форматированием кода.
CoffeeScript устраняет одну из основных проблем JavaScript — глобальные переменные. В JavaScript весьма легко объявить глобальную переменную, забыв добавить ключевое слово var
перед её именем. CoffeeScript решает эту проблему убирая глобальные переменные. После компиляции CoffeeScript оборачивает весь наш код в анонимную функцию, тем самым разделяя глобальную и локальную области видимости. Рассмотрим в качестве примера простое объявление переменной и присваивание ей значения:
myVariable = "test"
Скомпилированный JS-код:
var myVariable;
myVariable = "test";
Как вы можете видеть, в этом коде совершается присвоение локальной переменной. CoffeeScript делает шаг вперёд и предотвращает довольно распространённую ошибку JavaScript-разработчиков.
Однако иногда бывает полезно создать глобальную переменную. Вы можете сделать переменную свойством глобального объекта (в браузерах — window), либо с помощью следующего способа:
exports = this
exports.MyVariable = "foo-bar"
В корневом контексте this является глобальным объектом,и путём создания переменной exports вы делаете более понятным то, какие глобальные переменные задаются в вашем скрипте. Кроме того, такой способ позволяет использовать модули CommonJS, которые мы рассмотрим несколько позже.
CoffeeScript сокращает довольно крупное объявление функции небольшой стрелкой: ->. Функции могут помещаться в одну строку или быть мультистрочными. Последнее выражение в функции неявно возвращается. Иначе говоря, вам не нужно использовать ключевое слово return, если вы захотите, чтобы функция возвращала что-либо — просто поместите это что-либо в конце.
Давайте рассмотрим такой пример:
func = -> "bar"
Скомпилированный вариант:
var func;
func = function() {
return "bar";
};
Как вы можете видеть, стрелка (->) скомпилировалось в выражение function, а перед строкой "bar" автоматически добавилось ключевое слово return.
Я уже говорил ранее, что нет причин не использовать мультистрочные функции — главное, чтобы отступы были корректны.
func = ->
# An extra line
"bar"
Как быть с аргументами функций? Что же, CoffeeScript предоставляет возможность объявить аргументы в скобках перед стрелкой.
times = (a, b) -> a * b
CoffeeScript также поддерживает задание значений аргументов по умолчанию, например:
times = (a = 1, b = 2) -> a * b
Также вы можете использовать три точки (...), чтобы указать на то, что в функцию может быть передано несколько аргументов:
sum = (nums...) ->
result = 0
nums.forEach (n) -> result += n
result
В примере выше переменная nums является массивом со всеми переданными аргументами. Это не объект, а именно массив, так что вам не придётся беспокоиться об использовании Array.prototype.splice
или jQuery.makeArray()
.
trigger = (events...) ->
events.splice(1, 0, this)
this.constructor.trigger.apply(events)
В CoffeeScript функции могут быть вызваны так же, как и в JavaScript: со скобками () или с помощью apply() / call(). Также функции могут быть вызваны без скобок, как в Ruby или Python:
a = "Howdy!"
alert a
# Аналог:
alert(a)
alert inspect a
# Аналог:
alert(inspect(a))
Я рекомендую использовать скобки в тех случаях, когда не совсем очевидно, что вызывается и с какими аргументами. Например, в последней строчке примера я бы определенно поставил скобки.
alert inspect(a)
Отмечу, что если вы хотите вызвать функцию без аргументов, то CoffeeScript не сможет определить, намерены вы вызвать функцию или присвоить значение переменной. В этом плане поведение CoffeeScript несколько отличается от Ruby, в котором в таких случаях будет вызвана функция. Такое поведение также несколько схоже с поведением в Python. Эта тонкость была причиной нескольких ошибок в моих CoffeeScript-программах, поэтому внимательно отслеживайте случаи вызова функций без аргументов и добавляйте к ним круглые скобки.
В JavaScript часто изменяется контекст выполнения функции, особенно при работе с коллбэками событий. Поэтому CoffeeScript предоставляет инструменты для предотвращения ошибок при работе с контекстом. Одним из таких инструментов является «жирная» стрелка: =>
.
Использование «жирной» стрелки гарантирует, что контекст исполнения функции будет привязан к локальному. Например:
this.clickHandler = -> alert "clicked"
element.addEventListener "click", (e) => this.clickHandler(e)
Причина по которой вы можете воспользоваться таким способом — то, что коллбэк из addEventListener
будет выполнен в контексте элемента, то есть this
будет являться элементом. Если вы хотите использовать локальный контекст без костылей вида self = this
, «жирная» стрелка — это то, что вам нужно.
Эта идея привязки аналогична методу proxy()
из jQuery или методу bind()
из ES5.
Объекты могут быть определены так же, как и в JavaScript — с помощью пары фигурных скобок и конструкции вида свойство/значение внутри. Однако CoffeeScript не требует обязательного использования фигурных скобок. А ещё вы можете использовать отступы и переносы строк вместо постановки запятых после определения свойства.
object1 = {one: 1, two: 2}
# Без фигурных скобок
object2 = one: 1, two: 2
# Использование переноса строки вместо запятой
object3 =
one: 1
two: 2
User.create(name: "John Smith")
Массивы также могут использовать пробел вместо запятой в качестве разделителя. Однако, для массивов квадратные скобки ([]) требуются обязательно.
array1 = [1, 2, 3]
array2 = [
1
2
3
]
array3 = [1,2,3,]
В примере выше CoffeeScript при компиляции удалит запятую после последнего элемента массива array3
. Зачастую эта запятая является источником ошибок, но с CoffeeScript вам не придётся об этом беспокоиться.
Соглашение о необязательных круглых скобках действует и в отношении ключевых слов if/else.
if true == true
"We're ok"
if true != true then "Panic"
# Аналогично следующему:
# (1 > 0) ? "Ok" : "Y2K!"
if 1 > 0 then "Ok" else "Y2K!"
Как вы можете видеть выше, если всё условное выражение располагается на одной строке, вам придётся использовать ключевое слово then
, чтобы компилятор знал, где начинается следующий блок кода. Условные операторы (?:) не поддерживаются, вместо них следует использовать однострочное выражение if/else.
CoffeeScript также поддерживает условные выражения в стиле языка Ruby.
alert "It's cold!" if heat < 5
Вместо использования восклицательного знака (!) для отрицания вы можете использовать ключевое слово not
. Оно позволяет сделать код более читаемым во многих случаях.
if not true then "Panic"
В примере выше вместо if not
можно также использовать ключевое слово unless
.
unless true
"Panic"
Также в CoffeeScript есть ключевое слово is
, которое при компиляции заменяется на ===
.
if true is 1
"Type coercion fail!"
Как альтернативу выражению is not
можно использовать выражение isnt
.
if true isnt true
alert "Opposite day!"
Возможно вы заметили, что при компиляции CoffeeScript заменяет выражения ==
и !=
на выражения ===
и !==
соответственно. Это одна из самых простых и при этом самых полезных фич языка. В чём причина этого? Честно говоря, в JavaScript операторы ==
и !=
ведут себя несколько странно. Гораздо безопаснее использовать операторы ===
и !==
, так как они помимо прочего сравнивают типы двух операндов, и если эти типы отличаются, то конструкция вернёт false
.
CoffeeScript даёт возможность использовать Ruby-подобную интерполяцию строк. Строки в двойных кавычках могут содержать в себе конструкции вида #{}
. Внутри такой конструкции может быть обработан любой код, результат выполнения которого будет включён в строку.
favourite_color = "Blue. No, yel..."
question = "Bridgekeeper: What... is your favourite color?
Galahad: #{favourite_color}
Bridgekeeper: Wrong!
"
Если строка слишком длинная, её можно разбить на несколько строк, как в примере выше.
Перебор массивов в JavaScript имеет довольно архаичный синтаксис в стиле старых языков, а не в стиле современного объектно-ориентированного языка. Спецификация ES5 несколько исправляет эту ситуацию, вводя в обиход функцию forEach()
, однако это по-прежнему требует вызова функции каждую итерацию, что является причиной медленной работы кода. CoffeeScript же решает эту проблему весьма красиво:
for name in ["Roger", "Roderick", "Brian"]
alert "Release #{name}"
Если вам потребуется узнать номер текущей итерации, добавьте дополнительный аргумент:
for name, i in ["Roger the pickpocket", "Roderick the robber"]
alert "#{i} - Release #{name}"
Вы можете записать этот код в одну строку, используя постфиксную форму:
release prisoner for prisoner in ["Roger", "Roderick", "Brian"]
Также можно фильтровать элементы массива (как в языке Python):
prisoners = ["Roger", "Roderick", "Brian"]
release prisoner for prisoner in prisoners when prisoner[0] is "R"
Кроме того, вы можете перебирать свойства объекта, используя ключевое слово of
вместо in
.
names = sam: seaborn, donna: moss
alert("#{first} #{last}") for first, last of names
Единственный цикл, который ведёт себя так же, как и в чистом JavaScript, это цикл while
. Есть лишь небольшое отличие — в CoffeeScript этот цикл возвращает массив результатов, т.е. он работает примерно так же, как Array.prototype.map()
.
num = 6
minstrel = while num -= 1
num + " Brave Sir Robin ran away"
CoffeeScript весьма похож на Ruby в плане генерации массива с помощью диапазона значений. Диапазон определяется двумя числовыми значениями (начальная позиция и конечная позиция), разделённых двумя или тремя точками (..
или ...
).
range = [1..5]
Если диапазон будет указан после значения переменной, то CoffeeScript сконвертирует его в вызов метода slice()
.
firstTwo = ["one", "two", "three"][0..1]
В примере выше переменной будет присвоен новый массив, содержащий в себе только первые два элемента оригинального массива. Также вы можете использовать такой способ для замены части массива:
numbers = [0..9]
numbers[3..5] = [-3, -4, -5]
Интересно, что JavaScript позволяет вызвать метод slice()
и для строк, так что вы можете использовать такой же синтаксис для получения части строки.
my = "my string"[0..2]
Проверка наличия в массиве определённого значения всегда была скучной задачей. К тому же метод indexOf()
не полностью кроссбраузерный (да-да, IE, я говорю о тебе). CoffeeScript решает эту проблему с помощью оператора in
.
words = ["rattled", "roudy", "rebbles", "ranks"]
alert "Stop wagging me" if "ranks" in words
В CoffeeScript существуют алиасы, позволяющие сократить количество печатаемого кода. Одним из них является символ @
, который заменяет ключевое слово this
.
@saviour = true
Ещё один алиас — ::
— заменяет prototype
:
User::first = -> @records[0]
Использование условного оператора if
для проверки на значение null
в JavaScript является повсеместной практикой, однако этот способ имеет некоторые подводные камни: пустые строки и ноль при сравнении интерпретируются как false
. Оператор ?
в CoffeeScript возвращает true
, если переменная не является null
или undefined
. Такое же поведение имеет nil?
в языке Ruby.
praise if brian?
Также вы можете использовать этот оператор вместо ||
:
velocity = southern ? 40
Если вы используете проверку на null
перед непосредственным получением доступа к свойству или методу, то вы можете пропустить этот шаг, использовав оператор ?
перед вызовом свойства или метода.
blackKnight.getLegs()?.kick()
Подобным образом можно проверить свойство на то, является ли оно функцией. Для этого следует поставить оператор ?
между именем свойства и скобками. Если свойство не существует или оно не является функцией, то код попросту не будет выполнен.
blackKnight.getLegs().kick?()