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.
Ну и как положено, несколько выводов.
- Coffee увеличивает выразительность кода, упрощает и ускоряет как первоначальную разработку, так и дальнейшую поддержку кода.
- Обучение очень быстрое (мне хватило пару дней втянуться).
- Удобная поддержка со стороны WebStorm (Для других IDE тоже есть плагины, но про их качество ничего сказать не могу)
- Большое community
- Уберегает особенно начинающих разработчиков от многих ошибок.
Главное — понимать, что генерирует CoffeeScript. Тогда он превращается из лишнего подозрительного слоя абстракции в мощный инструмент.
ссылка на оригинал статьи http://habrahabr.ru/post/179031/
Добавить комментарий