Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/foxbunny/cstricks

CoffeeScript Tricks
https://github.com/foxbunny/cstricks

Last synced: 28 days ago
JSON representation

CoffeeScript Tricks

Awesome Lists containing this project

README

        

# CoffeeScript Tricks

These are some of the tricks I use from time to time to improve the code
readability and/or speed things up.

## Contents

+ [Immediately-invoked function expression](#iife)
+ [Iterating arrays in reverse](#reverse)
+ [Getting the last item in an array](#lastitem)
+ [Return or throw](#retthrow)
+ [Deluxe list comprehensions](#deluxecomp)
+ [Recursive function as object property](#recursiveprop)
+ [UMD wrapper](#umdwrapper)
+ [Clean up JavaScript by returning](#cleanreturn)

## Immediately-invoked function expression (IIFE)

This might not be such a 'big' trick, but here's the syntax:

((args...) ->

) args...

Yeah, nothing too spectacular here. Moving on.

## Iterating arrays in reverse

This one might also be obvious to most, but here it is anyway:

a = [1, 2, 3, 4]

for i in a by -1
console.log i

## Getting the last item in an array

Most people would think:

a[a.length - 1]

But there's a slightly better way:

a[-1..][0]

And, no, `a[-1]` doesn't work. And, yes, you can still use `a.pop()` if you
don't care about what happens to `a`.

## Return or throw

This is one of those patterns that look so obvious when written in
CoffeeScript, and yet it compiles into something quite delicious and hackish.

a = () ->
# calculate x
x or throw new Error 'OMFG!'

You just have to see what it looks like when compiled:

var a;

a = function() {
return x || (function() {
throw new Error('OMFG!');
})();
};

## Deluxe list comprehensions

Let's say we have an array of objects, and we want to transform that object in
some way and create a new array or simply overwrite the old one.

Let's say the array looks like this:

a = [
{a: 2, b: 4}
{a: 3, b: 5}
{a: 12, b: 0}
]

Now let's add a `c` key that will be sum of keys `a` and `b`:

a = (
((o) ->
a: o.a
b: o.b
c: o.a + o.b
) o for o in a
)

The `a = (expression for o in a)` is a classic list comprehension. For the
`expression` we use an IIFE which, extracted, looks like this:

(o) ->
a: o.a
b: o.b
c: o.a + o.b
) o

You can exclude the outer brackets of the list comprehensions provided that you
do not change the relative position in code of the actual comprehension. The
code below compiles to identical JavaScript as the code above:

a = # <-- look, mommy, no bracket!
((o) ->
a: o.a
b: o.b
c: o.a + o.b
) o for o in a

The reason CoffeeScript compiler works like this is beyond me, so don't ask.
The above form is a bit confusing, though, so let's just use brackets when
assigning to variables.

You're also guessing correctly that we can also use the deluxe version of
`when` in the comprehensions:

a = # <-- look, mommy, no bracket!
((o) ->
a: o.a
b: o.b
c: o.a + o.b
) o for o in a when ((o) ->
o.a > o.b
) o

When using the deluxe list comprehensions as a return value, you don't need to
include the outer brackets.

c = () ->
((o) ->
a: o.a
b: o.b
c: o.a + o.b
) o for o in a # <-- look, mommy, no brackets!

When using as return value, I think it's ok to just omit the brackets. It
doesn't make any difference at all.

## Recursive function as object property

You can use a recursive function as an object property. The problem is usually
how to call this function from within itself when it's being assigned as a
property. Take a look at this:

a =
b: () ->

How do you call `a.b` from within itself? Well, you can simply call it as
`@b()`

a =
b: (i, n=0) ->
return n if not i
@b(i - 1, n + i)

What if we want to use b stand-alone? For example, we want to do something
like:

b = a.b
b 12

In this case, we need `b` to refer to itsef using the reference to `a`:

a =
b: (i, n=0) ->
return n if not i
a.b(i - 1, n + i)

Alternatively, you can also do this:

a =
b: b = (i, n=0) ->
return n if not i
b(i - 1, n + i) # <-- `b` refers to `b` variable in same scope as `a`

This makes `b` both `a`'s property, and a stand-alone `b` variable within the
same scope where `a` is defined (this works in JavaScript as well).

## UMD wrapper

If you want to create an
[UMD](https://github.com/umdjs/umd/#umd-universal-module-definition) wrapper
for your AMD module, there are many ways to do it, including using IIFE, which
is a [recommendation by James
Burke](https://github.com/umdjs/umd/blob/master/commonjsAdapter.js). Here,
we'll take a look at an alternative syntax which works well in CoffeeScript
when using a CommonJS-style module that calls `require`.

The UMD wrapper below supports browsers without AMD loaders, as well as NodeJS
modules.

define = ((root) ->
if typeof root.define is 'function' and root.define.amd
root.define # Return RequireJS's version of define
else
if typeof module is 'object' and module.exports
(factory) -> module.exports = factory(root.require)
else
require = (dep) ->
(() ->
switch dep
when 'underscore' then root._
else null
)() or throw new Error "Unmet dependency #{dep}"
(factory) -> root.myModule = factory(require)
) this

define (require) ->
_ = require 'underscore'

The above wrapper may seem huge, but it actually takes care of dependencies and
also traps any missing dependencies when working with non-AMD browser
situation.

If the module has no dependencies, it can be simplified:

define = ((root) ->
if typeof root.define is 'function' and root.define.amd
root.define # Return RequireJS's version of define
else
if typeof module is 'object' and module.exports
(factory) -> module.exports = factory()
else
(factory) -> root.myModule = factory()
) this

define () ->

Further, if you do not need support for NodeJS, it's even simpler:

define = ((root) ->
if typeof root.define is 'function' and root.define.amd
root.define # Return RequireJS's version of define
else
(factory) -> root.myModule = factory()
) this

define () ->

Last but not least, if you do need dependencies, but don't need NodeJS:

define = ((root) ->
if typeof root.define is 'function' and root.define.amd
root.define # Return RequireJS's version of define
else
require = (dep) ->
(() ->
switch dep
when 'underscore' then root._
else null
)() or throw new Error "Unmet dependency #{dep}"
(factory) -> root.myModule = factory(require)
) this

define (require) ->
_ = require 'underscore'

## Cleaning up JavaScript by returning

If you are like me, you are probably used to coding CoffeeScript blind-folded
and you rarely look at the output code (unless something breaks, that is, and
maybe not even then). Sometimes, though, code that _works_ might not be as nice
in JavaScript as it seems in CoffeeScript. In some cases this is because in
CoffeeScript, everything is an expression, and the last expression evaluated
will be returned from a function.

Consider this example:

b = []

a = (x) ->
b.push i + 1 for i in x

This function returns the result of the list comprehension, because that is the
last expression in the function. It compiles to this handful:

var a, b;

b = [];

a = function(x) {
var i, _i, _len, _results;
_results = [];
for (_i = 0, _len = x.length; _i < _len; _i++) {
i = x[_i];
_results.push(b.push(i + 1));
}
return _results;
};
var a;

But clearly, in this case, we aren't really interested in the return value
which generated JavaScript frantically gathers and returns. In this instance,
we should simply `return` at the end of the function and let CoffeeScript know
that we don't expect any meaningful return value.

a = (x) ->
b.push i + 1 for i in x
return

This makes the JavaScript a lot slimmer, and depending on what you are doing,
much faster, too.

a = function(x) {
var i, _i, _len;
for (_i = 0, _len = x.length; _i < _len; _i++) {
i = x[_i];
b.push(i + 1);
}
};

## License

Creative
<br />Commons License

CoffeeScript Tricks by Branko Vukelic is licensed under a Creative Commons
Attribution 3.0 Unported License
.
Based on a work at https://github.com/foxbunny/cstricks.