Статья представляет собой не исчерпывающее описание языка программирования CoffeeScript, а именно знакомство, обзор некоторых интересных возможностей. Целевая аудитория — те, кто еще не смотрел в сторону CoffeeScript, но так или иначе используют JavaScript в своих проектах.
CoffeeScript — это маленький язык, который транслируется в JavaScript. Его документация умещается на одной странице — coffeescript.org и отличается компактностью и наглядностью. Я даже сомневался в необходимости данной статьи, когда есть такое классное описание «от производителя», но все же рискнул расставить акценты и прояснить некоторые детали.
Если копнуть немного истории, то с 2009-го года язык писался на Ruby, с 2010 — он пишется на самом же CoffeeScript.
И в Ruby on Rails, начиная с версии 3.1, он «заменил» JavaScript.
По сути CoffeeScript просто синтаксический сахар над JavaScript. А значит, его ценность в том, что он позволяет нагляднее выражать свои мысли и понимать чужие.
JavaScript (читай ECMAScript), конечно, тоже не стоит на месте, развивается. В том числе перенимая некоторые идеи из CoffeeScript. Но если говорить про кросс-браузерный JavaScript, то лично у меня большие подозрения, что светлое будущее с продвинутым JavaScript наступит скоро. А CoffeeScript уже сейчас позволяет наслаждаться плодами технологического прогресса.
В этом ключе нельзя не упомянуть TypeScript, в определенном смысле, конкурента CoffeeScript. Он позиционируется, как надмножество JavaScript, добавляя новые фичи в язык, во многом отражая будущее JavaScript. С этой позиции он интереснее.
Но у CoffeeScript, есть преимущество, что ему не нужно сохранять совместимость с JavaScript, что, по-моему, дает больше свободы и позволяет сделать язык более выразительным. Так что, как минимум одна заслуживающая внимания альтернатива CoffeeScript есть. Но вернемся к теме.
Хорошо, как пользоваться этим вашим CoffeeScript?
Удобнее всего, на мой взгляд, работать с ним, как с модулем node.js. Ставится он проще простого:
Создаем две папки, для определенности назовем их
Создаем файл
После этого запускаем ��ранслятор:
В итоге в папке
Конечно, на каждый чих запускать транслятор не интересно. Запуск команды
заставляет следить за всеми изменениями файлов в папке
Перейдем к самому языку. Напишем простенький код на CoffeeScript:
Его JavaScript-эквивалент:
Здесь мы создаем две функции, вычисляющие квадрат и куб числа соответственно.
Первым делом обратим внимание, что весь код спрятан внутри анонимной функции, которую мы сразу же вызываем.
Этот прием позволяет прятать все локальные переменные внутри функции, не заботясь о том, что они будут засорять глобальную область видимости. Ниже в статье мы будем опускать эту функцию для наглядности.
Далее обратим внимание, что объявления всех локальных переменных
Стрелочка
И еще обратите внимание, что нет необходимости добавлять слово
CoffeeScript добавляет значения по умолчанию для параметров функций, чего нет в JavaScript.
Пример на CoffeeScript:
Эквивалент на JavaScript:
JavaScript-реализация сводится проверке параметра
Другая деталь, которую иллюстрирует пример — в качестве выделения блоков используются не фигурные скобки, а отступы, как в Питоне.
Другая вещь, которая раздражает в JavaScript — очень многословная итерация по свойствам объектов.
Дело в том, что в большинстве случаев при обходе объекта интересуют его собственные свойства, а не свойства прототипа.
А делать каждый раз
Решение же в стиле jQuery.each() никто не запрещал, но оно уступает по эффективности дедовскому
Смотрим, как сделать круто:
Эквивалент:
В JavaScript оператор == ведет себя мягко говоря странно. Гораздо безопаснее использовать ===. Поэтому CoffeeScript преобразует оператор == в ===, оберегая начинающих разработчиков от подстерегающих в JavaScript ловушек. Хотя приходит в голову один случай, когда оператор == все-таки полезен. Это сравнение с
И на выходе:
Переходим к классам. На всякий случай уточним, что классами будем называть функции-конструкторы объектов.
Рассмотрим пример:
Даже интуитивно можно догадаться, что происходит. Описаны базовый класс
Обратим внимание на класс
В методах
Не буду томить и, наконец-то, перейдем к js-варианту наших классов.
В основе наследования лежит вариация классической функции
Реализация достаточно простая. Конечно, если сравнивать с другими JavaScript библиотеками, которые предоставляют удобную кросс-браузерную реализацию классов на чистом JavaScript.
Минус навороченных библиотек в том, что не всегда легко разобраться, как они работают внутри.
А функция
Еще достаточно важный критерий — эффективность генерируемого кода. Так вот, с этим все в порядке, никаких глупостей я не обнаружил. Функции как положено добавляются не как свойства класса, а в прототип. Также порадовало, что значение по умолчанию свойств класса тоже добавляются в прототип.
Рассмотрим очень простой класс:
На выходе имеем JavaScript:
Здесь используется, так называемая асимметричность свойств объекта на чтение и на запись.
В реальной жизни значение свойства по умолчанию практически всегда выгодней добавлять в прототип объекта.
Пока нам не понадобится изменить это значение по умолчанию, мы не тратим лишнюю память для каждого объекта определенного класса. Но, допустим, мы решили изменить значение данного свойства так:
Здесь создается персональное свойство
Единственное, что может смущать в этом подходе, что при обращении к свойству, которое находится в прототипе, приходится продвигаться по цепочке прототипов. А это дается не бесплатно. Но на современных движках это не существенно, тем более на фоне радикальной оптимизации использования памяти, ну а старые IE-ки, в которых ощущалась деградация, постепенно уходят в небытие.
Другая крутая фича — назначение обработчиков событий для методов объектов. Пример:
Выдача:
Чтобы в качестве обработчика события указать метод этого же объекта на чистом JavaScript, приходится выкручиваться.
Один из самых распространенных способов — создание замыкания. В CoffeeScript этот костыль не нужен. Достаточно функцию обработчика указать не как
Если потребуется подключить чистый JavaScript-код, то это также просто сделать:
На выходе получаем:
Ну и конечно, присутствует много фишек для работы с массивами и объектами. Для иллюстрации рассмотрим одну.
Например, пусть мы хотим получить массив кубов чисел от 1 до 5.
В CoffeeScript достаточно написать:
В многословном JavaScript получаем:
Надеюсь, для знакомства должно хватить. Дальше добро пожаловать на coffeescript.org.
Ну и как положено, несколько выводов:
Главное — понимать, что генерирует CoffeeScript. Тогда он превращается из лишнего подозрительного слоя абстракции в мощный инструмент.
CoffeeScript — это маленький язык, который транслируется в JavaScript. Его документация умещается на одной странице — coffeescript.org и отличается компактностью и наглядностью. Я даже сомневался в необходимости данной статьи, когда есть такое классное описание «от производителя», но все же рискнул расставить акценты и прояснить некоторые детали.
Введение
Если копнуть немного истории, то с 2009-го года язык писался на Ruby, с 2010 — он пишется на самом же CoffeeScript.
И в Ruby on Rails, начиная с версии 3.1, он «заменил» JavaScript.
По сути CoffeeScript просто синтаксический сахар над JavaScript. А значит, его ценность в том, что он позволяет нагляднее выражать свои мысли и понимать чужие.
JavaScript (читай ECMAScript), конечно, тоже не стоит на месте, развивается. В том числе перенимая некоторые идеи из CoffeeScript. Но если говорить про кросс-браузерный JavaScript, то лично у меня большие подозрения, что светлое будущее с продвинутым JavaScript наступит скоро. А CoffeeScript уже сейчас позволяет наслаждаться плодами технологического прогресса.
В этом ключе нельзя не упомянуть TypeScript, в определенном смысле, конкурента CoffeeScript. Он позиционируется, как надмножество JavaScript, добавляя новые фичи в язык, во многом отражая будущее JavaScript. С этой позиции он интереснее.
Но у CoffeeScript, есть преимущество, что ему не нужно сохранять совместимость с JavaScript, что, по-моему, дает больше свободы и позволяет сделать язык более выразительным. Так что, как минимум одна заслуживающая внимания альтернатива CoffeeScript есть. Но вернемся к теме.
Трансляция кода
Хорошо, как пользоваться этим вашим CoffeeScript?
Удобнее всего, на мой взгляд, работать с ним, как с модулем node.js. Ставится он проще простого:
npm install -g coffee-scriptСоздаем две папки, для определенности назовем их
lib и src.Создаем файл
src/helloWorld.coffee и напишем, что нибудь на CoffeeScript. Например:console.log('Hello world')
После этого запускаем ��ранслятор:
coffee --compile --output lib/ src/ В итоге в папке
lib будет лежать файл helloWorld.js, готовый к выполнению. Конечно, на каждый чих запускать транслятор не интересно. Запуск команды
coffee -o lib/ -cw src/ заставляет следить за всеми изменениями файлов в папке
src и самостоятельно транслировать их в JavaScript-код.Синтаксис
Функции
Перейдем к самому языку. Напишем простенький код на CoffeeScript:
square = (x) -> x * x cube = (x) -> square(x) * x
Его JavaScript-эквивалент:
(function() { var cube, square; square = function(x) { return x * x; }; cube = function(x) { return square(x) * x; }; }).call(this);
Здесь мы создаем две функции, вычисляющие квадрат и куб числа соответственно.
Первым делом обратим внимание, что весь код спрятан внутри анонимной функции, которую мы сразу же вызываем.
Этот прием позволяет прятать все локальные переменные внутри функции, не заботясь о том, что они будут засорять глобальную область видимости. Ниже в статье мы будем опускать эту функцию для наглядности.
Далее обратим внимание, что объявления всех локальных переменных
var cube, square вынесено в начало. Что защищает от распространенной ошибки, когда переменная не с того, не с сего стала глобальной из-за того, что банально забыли добавить объявление var. Стрелочка
-> заменяет слово function.И еще обратите внимание, что нет необходимости добавлять слово
return. Оно добавляется автоматически к последнему выражению в функции.Значения параметров по умолчанию
CoffeeScript добавляет значения по умолчанию для параметров функций, чего нет в JavaScript.
Пример на CoffeeScript:
fill = (container, liquid = "coffee") -> "Filling the #{container} with #{liquid}..."
Эквивалент на JavaScript:
var fill; fill = function(container, liquid) { if (liquid == null) { liquid = "coffee"; } return "Filling the " + container + " with " + liquid + "..."; };
JavaScript-реализация сводится проверке параметра
liquid на равенство null или undefined.Другая деталь, которую иллюстрирует пример — в качестве выделения блоков используются не фигурные скобки, а отступы, как в Питоне.
Итерация свойств объекта
Другая вещь, которая раздражает в JavaScript — очень многословная итерация по свойствам объектов.
Дело в том, что в большинстве случаев при обходе объекта интересуют его собственные свойства, а не свойства прототипа.
А делать каждый раз
for а в нем сразу же проверку hasOwnProperty немного утомляет.Решение же в стиле jQuery.each() никто не запрещал, но оно уступает по эффективности дедовскому
for. Смотрим, как сделать круто:
yearsOld = max: 10, ida: 9, tim: 11 for own child, age of yearsOld console.log "#{child} is #{age}"
Эквивалент:
var age, child, __hasProp = {}.hasOwnProperty; for (child in yearsOld) { if (!__hasProp.call(yearsOld, child)) continue; age = yearsOld[child]; console.log("" + child + " is " + age); }
Приятные мелочи
В JavaScript оператор == ведет себя мягко говоря странно. Гораздо безопаснее использовать ===. Поэтому CoffeeScript преобразует оператор == в ===, оберегая начинающих разработчиков от подстерегающих в JavaScript ловушек. Хотя приходит в голову один случай, когда оператор == все-таки полезен. Это сравнение с
null, которое позволяет проверить null и undefined одним махом. В CoffeeScript для этого предназначен оператор ?. Рассмотрим пример:alert "I knew it!" if elvis?
И на выходе:
if (typeof elvis !== "undefined" && elvis !== null) { alert("I knew it!"); }
Классы
Переходим к классам. На всякий случай уточним, что классами будем называть функции-конструкторы объектов.
Рассмотрим пример:
class Animal constructor: (@name) -> move: (meters) -> alert @name + " moved #{meters}m." class Snake extends Animal move: -> alert "Slithering..." super 5 class Horse extends Animal move: -> alert "Galloping..." super 45 sam = new Snake "Sammy the Python" tom = new Horse "Tommy the Palomino" sam.move() tom.move()
Даже интуитивно можно догадаться, что происходит. Описаны базовый класс
Animal и два его наследника: Snake и Horse.Обратим внимание на класс
Animal. Запись @name в параметрах конструктора — это удобное сокращение, которое определяет свойство класса name и автоматически присваивает ему значение, передаваемое в конструкторе. В методе move запись @name — сокращение от this.name.В методах
move в подклассах super вызывает родительский метод с тем же названием. Ведь, и правда, когда мы находимся в дочернем классе, ссылка на родителя бывает нужна только для того, чтобы обратиться к одноименному методу родительского класса. Другие случаи даже в голову не приходят. Не буду томить и, наконец-то, перейдем к js-варианту наших классов.
var Animal, Horse, Snake, sam, tom, _ref, _ref1, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; Animal = (function() { function Animal(name) { this.name = name; } Animal.prototype.move = function(meters) { return alert(this.name + (" moved " + meters + "m.")); }; return Animal; })(); Snake = (function(_super) { __extends(Snake, _super); function Snake() { _ref = Snake.__super__.constructor.apply(this, arguments); return _ref; } Snake.prototype.move = function() { alert("Slithering..."); return Snake.__super__.move.call(this, 5); }; return Snake; })(Animal); Horse = (function(_super) { __extends(Horse, _super); function Horse() { _ref1 = Horse.__super__.constructor.apply(this, arguments); return _ref1; } Horse.prototype.move = function() { alert("Galloping..."); return Horse.__super__.move.call(this, 45); }; return Horse; })(Animal); sam = new Snake("Sammy the Python"); tom = new Horse("Tommy the Palomino"); sam.move(); tom.move();
В основе наследования лежит вариация классической функции
extend.Реализация достаточно простая. Конечно, если сравнивать с другими JavaScript библиотеками, которые предоставляют удобную кросс-браузерную реализацию классов на чистом JavaScript.
Минус навороченных библиотек в том, что не всегда легко разобраться, как они работают внутри.
А функция
extend очень хорошо описана во множестве источников, например, здесь javascript.ru/tutorial/object/inheritance#nasledovanie-na-klassah-funkciya-extend.Эффективность
Еще достаточно важный критерий — эффективность генерируемого кода. Так вот, с этим все в порядке, никаких глупостей я не обнаружил. Функции как положено добавляются не как свойства класса, а в прототип. Также порадовало, что значение по умолчанию свойств класса тоже добавляются в прототип.
Рассмотрим очень простой класс:
class Foo bar: 10
На выходе имеем JavaScript:
var Foo; Foo = (function() { function Foo() {} Foo.prototype.bar = 10; return Foo; })();
Здесь используется, так называемая асимметричность свойств объекта на чтение и на запись.
В реальной жизни значение свойства по умолчанию практически всегда выгодней добавлять в прототип объекта.
Пока нам не понадобится изменить это значение по умолчанию, мы не тратим лишнюю память для каждого объекта определенного класса. Но, допустим, мы решили изменить значение данного свойства так:
obj = new Foo() obj.bar = 500
Здесь создается персональное свойство
bar у объекта obj. При этом свойство bar прототипа объекта obj по-прежнему равно 10. Все безопасно и эффективно.Единственное, что может смущать в этом подходе, что при обращении к свойству, которое находится в прототипе, приходится продвигаться по цепочке прототипов. А это дается не бесплатно. Но на современных движках это не существенно, тем более на фоне радикальной оптимизации использования памяти, ну а старые IE-ки, в которых ощущалась деградация, постепенно уходят в небытие.
Назначение обработчиков событий
Другая крутая фича — назначение обработчиков событий для методов объектов. Пример:
Account = (customer, cart) -> @customer = customer @cart = cart $('.shopping_cart').bind 'click', (event) => @customer.purchase @cart
Выдача:
var Account; Account = function(customer, cart) { var _this = this; this.customer = customer; this.cart = cart; return $('.shopping_cart').bind('click', function(event) { return _this.customer.purchase(_this.cart); }); };
Чтобы в качестве обработчика события указать метод этого же объекта на чистом JavaScript, приходится выкручиваться.
Один из самых распространенных способов — создание замыкания. В CoffeeScript этот костыль не нужен. Достаточно функцию обработчика указать не как
->, а =>. После этого this внутри обработчика будет ссылаться на базовый объект. Интеграция с чистым JavaScript
Если потребуется подключить чистый JavaScript-код, то это также просто сделать:
hi = `function() { return [document.title, "Hello JavaScript"].join(": "); }`
На выходе получаем:
var hi; hi = function() { return [document.title, "Hello JavaScript"].join(": "); };
Массивы
Ну и конечно, присутствует много фишек для работы с массивами и объектами. Для иллюстрации рассмотрим одну.
Например, пусть мы хотим получить массив кубов чисел от 1 до 5.
В CoffeeScript достаточно написать:
cubes = (Math.pow(num, 3) for num in [1..5])
В многословном JavaScript получаем:
var cubes, num; cubes = (function() { var _i, _results; _results = []; for (num = _i = 1; _i <= 5; num = ++_i) { _results.push(Math.pow(num, 3)); } return _results; })();
Заключение
Надеюсь, для знакомства должно хватить. Дальше добро пожаловать на coffeescript.org.
Ну и как положено, несколько выводов:
- CoffeeScript увеличивает выразительность кода, упрощает и ускоряет как первоначальную разработку, так и дальнейшую поддержку кода.
- Обучение очень быстрое (мне хватило пару дней втянуться).
- Удобная поддержка со стороны WebStorm (Для других IDE тоже есть плагины, но про их качество ничего не могу сказать)
- Большое community
- Уберегает особенно начинающих разработчиков от многих ошибок.
Главное — понимать, что генерирует CoffeeScript. Тогда он превращается из лишнего подозрительного слоя абстракции в мощный инструмент.
