Знакомство с CoffeeScript

от автора

Статья представляет собой не исчерпывающее описание языка программирования 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. Ставится он проще простого:
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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *