The article is a non-exhaustive description of the CoffeeScript programming language, namely acquaintance, an overview of some interesting features. Target audience - those who have not looked in the direction of CoffeeScript, but somehow use JavaScript in their projects.
CoffeeScript is a small language that is translated to JavaScript. Its documentation fits on one page -
coffeescript.org and is compact and clear. I even doubted the need for this article, when there is such a cool description “from the manufacturer”, but I still ventured to place accents and clarify some details.
Introduction
If you dig a little history, then since 2009, the language has been written in Ruby, since 2010 - it is written in CoffeeScript itself.
And in Ruby on Rails, starting from version 3.1, it “replaced” JavaScript.
CoffeeScript is essentially just syntactic sugar over javascript. So, its value is that it allows you to more clearly express your thoughts and understand others.
')
JavaScript (read ECMAScript), of course, also does not stand still, evolves. Including adopting some ideas from CoffeeScript. But if we talk about cross-browser JavaScript, then personally I have great suspicions that a bright future with advanced JavaScript will come soon. And CoffeeScript now allows you to enjoy the fruits of technological progress.
In this vein, we can not fail to mention TypeScript, in a certain sense, the competitor CoffeeScript. It is positioned as a superset of JavaScript, adding new features to the language, largely reflecting the future of JavaScript. From this position it is more interesting.
But CoffeeScript has the advantage that it does not need to maintain compatibility with JavaScript, which, in my opinion, gives more freedom and makes the language more expressive. So, at least one noteworthy alternative to CoffeeScript is. But back to the topic.
Code translation
Well, how to use this your CoffeeScript?
It is most convenient, in my opinion, to work with it as with the node.js module. It is easy to put:
npm install -g coffee-script
Create two folders, for definiteness, let's call them
lib
and
src
.
Create a file
src/helloWorld.coffee
and write something on CoffeeScript. For example:
console.log('Hello world')
After this, run the translator:
coffee --compile --output lib/ src/
As a result, the
helloWorld.js
file will be in the
lib
folder, ready to be executed.
Of course, for every sneeze, running the translator is not interesting. Running a command
coffee -o lib/ -cw src/
forces you to monitor all changes to files in the
src
folder and independently translate them into JavaScript code.
Syntax
Functions
Let's go to the language itself. Let's write a simple code in CoffeeScript:
square = (x) -> x * x cube = (x) -> square(x) * x
Its JavaScript equivalent is:
(function() { var cube, square; square = function(x) { return x * x; }; cube = function(x) { return square(x) * x; }; }).call(this);
Here we create two functions that calculate the square and the cube of the number, respectively.
First of all, note that all the code is hidden inside an anonymous function, which we immediately call.
This technique allows you to hide all local variables inside the function, without worrying about cluttering the global scope. Below in the article we will omit this function for clarity.
Next, note that the declarations of all local variables
var cube, square
rendered in the beginning. What protects against a common mistake, when a variable hasn't, it has become global because of the banal forgot to add the
var
declaration.
The arrow
->
replaces the word
function
.
And also note that there is no need to add the word
return
. It is added automatically to the last expression in the function.
Default Parameter Values
CoffeeScript adds default values ​​for function parameters, which is not in JavaScript.
CoffeeScript example:
fill = (container, liquid = "coffee") -> "Filling the #{container} with #{liquid}..."
JavaScript equivalent:
var fill; fill = function(container, liquid) { if (liquid == null) { liquid = "coffee"; } return "Filling the " + container + " with " + liquid + "..."; };
The JavaScript implementation reduces checking the
liquid
parameter to be
null
or
undefined
.
Another detail that the example illustrates is that not braces are used as block selections, but padding, as in Python.
Object property iteration
Another thing that annoys JavaScript is a very verbose iteration on the properties of objects.
The fact is that in most cases when traversing an object, its own properties, and not the properties of the prototype, are of interest.
And to do each time
for
and immediately the
hasOwnProperty
check
hasOwnProperty
bit tiring.
The solution in the style of jQuery.each () was not forbidden by anyone, but it is inferior in efficiency to the old one
for
.
We look, how to make cool:
yearsOld = max: 10, ida: 9, tim: 11 for own child, age of yearsOld console.log "#{child} is #{age}"
Equivalent:
var age, child, __hasProp = {}.hasOwnProperty; for (child in yearsOld) { if (!__hasProp.call(yearsOld, child)) continue; age = yearsOld[child]; console.log("" + child + " is " + age); }
Pleasant trifles
In JavaScript operator == behaves mildly strange. It is much safer to use ===. Therefore, CoffeeScript converts the == operator to ===, protecting novice developers from trapping JavaScript traps. Although one case comes to mind when the operator == is still useful. This is a
null
comparison that allows you to check
null
and
undefined
one fell swoop. In CoffeeScript, is the operator intended for this
?
. Consider an example:
alert "I knew it!" if elvis?
And at the exit:
if (typeof elvis !== "undefined" && elvis !== null) { alert("I knew it!"); }
Classes
Go to the classes. Just in case, we specify that the classes will be called the function constructors of objects.
Consider an example:
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()
Even intuitively, you can guess what is happening. The base class
Animal
and its two heirs are described:
Snake
and
Horse
.
Pay attention to the class
Animal
. The
@name
in the constructor parameters is a convenient shortcut that defines a property of the name class and automatically assigns it the value passed in the constructor. In the move method, the
@name
entry is short for
this.name
.
In the
move
methods in
super
subclasses, it calls the parent method with the same name. After all, the truth is, when we are in a child class, a reference to the parent is needed only to refer to the same method of the parent class. Other cases do not even occur.
I will not torment and, at last, we will pass to the js-option of our classes.
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();
The inheritance is based on a variation of the classic
extend
function.
The implementation is quite simple. Of course, when compared with other JavaScript libraries that provide a convenient cross-browser implementation of classes in pure JavaScript.
The disadvantage of sophisticated libraries is that it is not always easy to figure out how they work inside.
And the
extend
function is very well described in a variety of sources, for example, here
javascript.ru/tutorial/object/inheritance#nasledovanie-na-klassah-funkciya-extend .
Efficiency
Another very important criterion is the efficiency of the generated code. So, this is all right, I did not find any nonsense. Functions as it should be added not as properties of the class, but in the prototype. Also pleased that the default value of class properties is also added to the prototype.
Consider a very simple class:
class Foo bar: 10
At the exit, we have JavaScript:
var Foo; Foo = (function() { function Foo() {} Foo.prototype.bar = 10; return Foo; })();
Here, the so-called asymmetry of the object properties for reading and writing is used.
In real life, the value of the default property is almost always more profitable to add to the prototype object.
As long as we do not need to change this default value, we do not waste extra memory for each object of a particular class. But let's say we decided to change the value of this property as follows:
obj = new Foo() obj.bar = 500
This is where the personal property
bar
the
obj
object is created. At the same time, the
bar
property of the prototype of the
obj
object is still equal to 10. Everything is safe and efficient.
The only thing that can be embarrassing in this approach is that when accessing the property that is in the prototype, you have to move along the chain of prototypes. And this is not given for free. But on modern engines it is not significant, especially on the background of a radical optimization of memory usage, and well, old IEs that felt degradation gradually fade away.
Assign event handlers
Another cool feature is the assignment of event handlers for object methods. Example:
Account = (customer, cart) -> @customer = customer @cart = cart $('.shopping_cart').bind 'click', (event) => @customer.purchase @cart
Issuance:
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); }); };
In order to specify the method of the same object on pure JavaScript as an event handler, you have to get out.
One of the most common ways is to create a closure. In CoffeeScript, this crutch is not needed. It is enough to specify the handler function not as
->
, but
=>
. After this,
this
inside the handler will refer to the base object.
Integration with pure javascript
If you need to connect pure JavaScript code, this is also easy to do:
hi = `function() { return [document.title, "Hello JavaScript"].join(": "); }`
At the output we get:
var hi; hi = function() { return [document.title, "Hello JavaScript"].join(": "); };
Arrays
And of course, there are a lot of chips to work with arrays and objects. For illustration, consider one.
For example, suppose we want to get an array of cubes of numbers from 1 to 5.
In CoffeeScript, just write:
cubes = (Math.pow(num, 3) for num in [1..5])
In verbose JavaScript, we get:
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; })();
Conclusion
I hope for dating should be enough. Further welcome to
coffeescript.org .
Well, as expected, several conclusions:
- CoffeeScript increases the expressiveness of the code, simplifies and speeds up both the initial development and further support of the code.
- The training is very fast (it took me a couple of days to get involved).
- Convenient support from WebStorm (For other IDEs, there are also plugins, but I can't say anything about their quality)
- Big community
- Saves especially novice developers from many mistakes.
The main thing is to understand what CoffeeScript generates. Then it turns from an extra suspicious layer of abstraction into a powerful tool.