📜 ⬆️ ⬇️

Meet CoffeeScript

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:

The main thing is to understand what CoffeeScript generates. Then it turns from an extra suspicious layer of abstraction into a powerful tool.

Source: https://habr.com/ru/post/179031/


All Articles