CoffeeScript — маленький крошечный язык, который компилируется в JavaScript. Во время выполнения интерпретации не существует, поскольку вы пишете CoffeeScript, компилируете его в JavaScript и используете полученные файлы JavaScript для своего приложения. Вы можете использовать любую библиотеку JavaScript (например, jQuery) из CoffeeScript, просто используя ее функции с соответствующим синтаксисом CoffeeScript. CoffeeScript может использоваться как для написания JavaScript на внешнем интерфейсе, так и на JavaScript на внутреннем.
Так почему CoffeeScript?
Меньше кода
Согласно » Маленькой книге» о CoffeeScript , синтаксис CoffeeScript уменьшает количество символов, которое необходимо набрать, чтобы заставить работать JS, примерно на 33-50%. Я представлю простую игру Tic-Tac-Toe, созданную с использованием CoffeeScript (вы, наверное, уже догадались об этом из названия), которая в своем необработанном формате CoffeeScript содержит 4963 символа, тогда как скомпилированный код JavaScript содержит 7669 символов. Это разница в 2706 символов или 36%!
Время разработки быстрее
Поскольку вы пишете короче, менее подвержены ошибкам (например, переменные имеют автоматическую область видимости, что означает, что вы не можете случайно перезаписать глобальные переменные, пропустив var
), вы сможете быстрее завершить свои проекты. Краткий синтаксис CoffeeScript также обеспечивает более читаемый код и, в конечном итоге, код, который легче поддерживать.
Начиная
В этой статье мы будем создавать простую игру в крестики-нолики с CoffeeScript и jQuery. Если вы хотите ознакомиться с синтаксисом до изучения практического примера, я предлагаю мою статью « Ускорить разработку JavaScript с использованием CoffeeScript» здесь, на SitePoint. Здесь также подробно описано, как установить CoffeeScript через npm (менеджер пакетов Node).
Как всегда, весь код этого учебного руководства доступен на GitHub, а демонстрационная версия доступна на CodePen или в конце учебного руководства .
Наиболее распространенные команды CoffeeScript, которые вы будете использовать:
coffee -c fileName
скомпилирует файл CoffeeScript в файл с тем же именем, но с расширением .js
(файлы CoffeeScript обычно имеют расширение .coffee
).
coffee -cw fileName
будет следить за изменениями в файле (при каждом сохранении файла) и компилировать его.
coffee -cw folderName/
будет следить за изменениями всех файлов .coffee
в папке и компилировать их в тот же каталог, когда будут какие-либо изменения.
Наконец, удобно скомпилировать CoffeeScript из папки с файлами .coffee
в папку, содержащую только файлы .js
.
coffee -o js/ -cw /coffee
будет следить за изменениями во всех файлах .coffee
расположенных в папке coffee
и помещать вывод (JavaScript) в папку js
.
Если вы не в терминалах, вы можете использовать инструмент с графическим интерфейсом для обработки ваших файлов CoffeeScript. Например, вы можете попробовать Prepros на бесплатной неограниченной пробной версии (хотя вы должны купить его, если вам это нравится). На рисунке ниже показаны некоторые опции, которые он предоставляет:
Вы можете видеть, что Prepros делает всю работу за вас — он настраивает средства наблюдения, чтобы ваши файлы .coffee
были скомпилированы в JS, он позволяет вам использовать Uglify JS, который минимизирует / сжимает ваш код, он может автоматически манипулировать переменными и поддерживает Замороженный CoffeeScript . Prepros также можно использовать для препроцессоров CSS, таких как Less и Sass, и шаблонизаторов, таких как Jade.
Игра
Давайте начнем с разметки:
<div class="wrapper"> <header class="text-center"> <h1>Tic Tac Toe</h1> </header> <div id="board"></div> <div class="alerts welcome"></div> <div class="notifications"></div> <form action="" method="POST"> ... </form> </div> <script src="jquery.min.js"></script> <script src="logic/app.js"></script>
Интерфейс игры состоит из следующего:
- Заголовок, который кратко описывает игру
- Элемент div с id
board
которой будут расположены квадраты 3 × 3 - Элемент div с классом
alerts
котором будет отображаться статус игры - Элемент div с классом
notifications
который покажет, кто играет в X и O, вместе с общей статистикой игрока. - Форма, которая будет отображаться только при загрузке игры и предложит игрокам ввести свои имена.
В соответствии с лучшими практиками, jQuery и скрипт, который делает тик нашего приложения, загружаются перед закрывающим тегом body.
Стиль
Используя CSS, мы можем сделать так, чтобы девять задействованных квадратов отображались в сетке 3 × 3, плавая каждый квадрат и очищая каждый четвертый.
.square:nth-of-type(3n + 1) { clear: both; }
Мы также можем добавить другой цвет к квадратам в зависимости от того, есть ли у них класс x
или o
(который добавляется с помощью JavaScript).
.square.x { color: crimson; } .square.o { color: #3997ff; }
CoffeeScript в действии
Для справки вы можете найти основной файл CoffeeScript здесь .
Вы можете видеть, что наше приложение Tic-Tac-Toe начинается с $ ->
, это эквивалентно сокращению для функции jQuery, которая выполняет код, когда DOM готов: $(function() { ... });
,
CoffeeScript полагается не на точки с запятой и фигурные скобки, а на отступ. ->
сообщает CoffeeScript, что вы определяете функцию, чтобы вы могли запустить тело функции на следующей строке и сделать отступ для тела с двумя пробелами.
Затем мы создаем объект с именем Tic
который сам содержит объект с именем data
. Вы можете видеть, что фигурные скобки или запятые не являются обязательными при создании объектов, если вы правильно сделаете отступ в свойствах.
$ -> Tic = data: turns: 0 x: {} o: {} gameOver: false
Свойство turns
будет содержать общее количество ходов, совершенных в игре. Мы можем проверить, имеет ли оно четное или неравное число, и таким образом определить, является ли это поворотом X или O.
Свойства x
и o
являются объектами и будут содержать данные, относящиеся к числу X или O по трем осям, которые важны для игры: горизонтальная, вертикальная и диагональная. Они будут обновляться при каждом перемещении через метод checkEnd
для представления распределения X и O на доске. Затем метод checkEnd
вызовет checkWin
чтобы определить, есть ли победитель.
После этого у нас есть метод внутри объекта Tic
, который все запустит и запустит:
initialize: -> @data.gameOver = false @.setPlayerNames() @.retrieveStats() @.assignRoles() @.prepareBoard() @.updateNotifications() @.addListeners()
Обратите внимание на использование @
которое компилируется в ключевое слово JavaScript this
. Как показано в первом свойстве initialize
, вы можете пропустить точку после ключевого слова @
при установке или вызове свойства или метода.
Давая методам разумные имена, мы имеем четкое представление о том, что они делают:
-
setPlayerNames
сохраняет значения, введенные пользователями во входныхdata
объектdata
. -
retrieveStats
извлекает статистику игрока из localStorage и устанавливает ее в объектеdata
. -
assignRoles
определяет, кто играет на X, а кто на O. -
prepareBoard
скрывает форму, удаляет любые уведомления, очищает доску и заполняет ее девятью пустыми клетками. -
updateNotifications
обновляет пользовательский интерфейс с информацией о том, кто играет в X и кто играет в О, а также статистику игрока. -
addListeners
присоединяет слушателей событий, чтобы мы могли отвечать наaddListeners
игроков.
Дайвинг глубже
Давайте посмотрим на пару из этих методов более подробно.
prepareBoard: -> ... $("<div>", {class: "square"}).appendTo("#board") for square in [0..8]
Здесь мы повторяем девять раз и добавляем девять делений с классом square
к пустой доске, чтобы заполнить ее. Это демонстрирует, как CoffeeScript позволяет писать однострочные циклы и объявлять тело цикла перед написанием самого условия.
updateNotifications: -> $(".notifications").empty().show() @.addNotification "#{@data.player1} is playing #{@data.rolep1}" ...
CoffeeScript допускает интерполяцию строк, что повышает удобочитаемость и уменьшает сложность и длину кода. Вы можете добавить #{}
в любую строку и вставить любую переменную или возвращаемое значение из вызова функции в фигурных скобках.
addNotification: (msg) -> $(".notifications").append($("<p>", text: msg));
Метод addNotification
иллюстрирует, как вы определяете параметры в CoffeeScript. Вы пишете их перед стрелкой ( ->
):
Вы можете предоставить значения по умолчанию для параметров, аналогичных PHP:
addNotification: (msg = "I am a message") ->
Когда функция с параметром по умолчанию компилируется, она преобразуется в:
if (msg == null) { msg = "I am a message"; }
Наконец, давайте обратимся к методу addListeners
:
addListeners: -> $(".square").click -> if Tic.data.gameOver is no and not $(@).text().length if Tic.data.turns % 2 is 0 then $(@).html("X").addClass("x moved") else if Tic.data.turns % 2 isnt 0 then $(@).html("O").addClass("o moved") ...
Здесь мы видим, что CoffeeScript предлагает дополнительные ключевые слова для представления истинных и ложных значений, таких как no
, yes
, off
и on
. Кроме того !==
, ===
, &&
!
может быть представлен с использованием isnt
, is
и not
соответственно.
Вы можете создать удобочитаемые однострочные условия, используя синтаксис if ... then ... else ...
Механика игры
Метод рабочей лошадки checkEnd
проверяет, есть ли победитель каждый раз, когда игрок делает ход. Это делается путем итерации по доске и подсчета квадратов, принадлежащих X и О. Сначала он проверяет диагональные оси, затем вертикальную и горизонтальную.
checkEnd : -> @.data.x = {} @.data.o = {} #diagonal check diagonals = [[0,4,8], [2,4,6]] for diagonal in diagonals for col in diagonal @.checkField(col, 'diagonal') @.checkWin() @.emptyStorageVar('diagonal') for row in [0..2] start = row * 3 end = (row * 3) + 2 middle = (row * 3) + 1 #vertical check @checkField(start, 'start') @checkField(middle, 'middle') @checkField(end, 'end') @checkWin() #horizontal check for column in [start..end] @checkField(column, 'horizontal') @checkWin() @emptyStorageVar('horizontal')
Как видите, здесь используется другая удобная функция CoffeeScript — диапазоны.
for row in [0..2]
Это зациклится три раза, установив ряд, равный 0, 1 и 2 в этом порядке. В качестве альтернативы, [0...2]
(исключительный диапазон) приведет к двум итерациям, установив строку равной 0 и 1.
В горизонтальной проверке мы снова видим, как отступ имеет решающее значение для определения того, что является частью цикла, а что находится вне цикла — только checkField
вызов checkField
находится внутри внутреннего цикла.
Вот как выглядит checkField
:
checkField: (field, storageVar) -> if $(".square").eq(field).hasClass("x") if @.data.x[storageVar]? then @.data.x[storageVar]++ else @.data.x[storageVar] = 1 else if $(".square").eq(field).hasClass("o") if @.data.o[storageVar]? then @.data.o[storageVar]++ else @.data.o[storageVar] = 1
Этот метод демонстрирует использование ?
ключевое слово, которое при вставке рядом с переменной в условном выражении компилируется в:
if (typeof someVariable !== "undefined" && someVariable !== null) {
Что, очевидно, очень удобно.
Метод checkField
добавляет единицу к соответствующей оси свойства x
или o
зависимости от имени класса квадрата, по которому щелкнули. Имя класса добавляется, когда пользователь нажимает на пустой квадрат доски в методе addListeners
.
Это подводит нас к методу checkWin
, который используется для проверки, выиграл ли кто-либо из игроков:
checkWin: -> for key,value of @.data.x if value >= 3 localStorage.x++ @showAlert "#{@.getPlayerName("X")} wins" @data.gameOver = true @addToScore("X") for key,value of @.data.o if value >= 3 localStorage.o++ @showAlert "#{@.getPlayerName("O")} wins" @data.gameOver = true @addToScore("O")
В CoffeeScript вы можете использовать for ... in array
для циклического перебора значений массива, а for key,value of object
для циклического перебора свойств объекта. checkWin
использует это для проверки всех свойств внутри объектов x
и o
. Если кто-либо из них имеет число, большее или равное трем, у нас есть победитель, и игра должна закончиться. В таком случае мы вызываем метод addToScore
который сохраняет результаты игроков через localStorage
.
Слово о локальном хранилище
LocalStorage является частью спецификации веб-хранилища и имеет довольно хорошую поддержку браузера . Он позволяет вам хранить данные (аналогичные файлам cookie) на компьютере пользователя и получать к ним доступ в любое время.
Вы можете получить доступ к API несколькими способами, например, так же, как и к свойствам обычного объекта:
//fetch item localStorage.myProperty // set item localStorage.myProperty = 123
Локальное хранилище всегда сохраняет строки, поэтому, если вы хотите сохранить объект или массив, вам придется использовать JSON.stringify
при хранении массива / объекта и JSON.parse
при его получении.
Наш метод addToScore
использует этот факт:
addToScore: (winningParty) -> ... if winningParty is "none" @.showAlert "The game was a tie" else ... localStorage[@data.player1] = JSON.stringify @data.p1stats
В нем также показано, как можно опустить скобки в CoffeeScript ( JSON.stringify
), хотя это рекомендуется только для самых внешних вызовов функций.
Далее у нас есть пара полезных методов. Мы используем emptyStorageVar
чтобы очистить содержимое определенной горизонтальной строки или диагонали. Это необходимо, потому что на плате две диагонали, и внутри нашего метода chekEnd
мы используем одно и то же свойство данных для обеих диагоналей. Поэтому мы должны очистить свойство перед проверкой второй диагонали. То же самое касается горизонтальных рядов.
emptyStorageVar: (storageVar) -> @.data.x[storageVar] = null @.data.o[storageVar] = null
Получение имен игроков
Когда форма с именами игроков отправляется в начале игры, мы можем предотвратить ее действие по умолчанию и обработать отправку с помощью JavaScript. Мы проверяем, есть ли пустое имя, и совпадают ли оба имени, и отображаем дружеское предупреждение, если это так. В противном случае мы запускаем игру, вызывая Tic.initialize()
.
$("form").on "submit", (evt) -> evt.preventDefault() $inputs = $("input[type='text']") namesNotEntered = $inputs.filter(-> return @.value.trim() isnt "" ).length isnt 2 namesIndentical = $inputs[0].value is $inputs[1].value if namesNotEntered then Tic.showAlert("Player names cannot be empty") else if namesIndentical then Tic.showAlert("Player names cannot be identical") else Tic.initialize()
В последней строке используется делегирование события, чтобы любой элемент с классом play-again
реагировал на нажатие. Делегирование событий необходимо, так как этот элемент добавляется на страницу только после завершения игры. Он отсутствует при первом отображении DOM.
$("body").on("click", ".play-again", -> Tic.initialize())
Собираем все вместе
Вот и все. Менее чем в 150 строках CoffeeScript у нас есть рабочая игра. Не забудьте, вы можете скачать код из этого урока с GitHub .
Вывод
Я надеюсь, что это руководство укрепило ваши знания CoffeeScript и покажет, как jQuery и CoffeeScript могут работать вместе. Есть много вещей, которые вы можете сделать, чтобы улучшить игру. Например, вы можете добавить опцию, чтобы сделать доску отличной от стандартных размеров 3 × 3. Вы могли бы реализовать некоторый простой ИИ, чтобы игроки могли играть против машины, или вы могли бы внедрять бомбы в игре, например, добавляя случайные Х или О на случайном игровом ходу, пока игроки сражаются за славу.