ちっちゃいCoffeeScriptの本

イディオム

すべての言語にはイディオムやプラクティスのセットがあり、CoffeeScriptも例外ではありません。この節では、これらのルールをについて学び、言語の実用的な感覚を得ることができるように、比較のためにいくつかのJavaScriptが同時に表示したいと思います。

Each

JavaScriptでは、新しく追加された forEach() か、一般的な反復処理を使う事が出来ます。ECMAScript 5で導入されたJavaScriptの最新の機能を使用したい場合、古いブラウザ向けにはshimの導入をお勧めします。

for (var i=0; i < array.length; i++)
  myFunction(array[i]);

array.forEach(function(item, i){
  myFunction(item)
});

forEach() 構文ははるかに簡潔で読みやすいですが、一方でコールバック関数が反復ごとに呼ばれているため、JavaScriptで書かれるループのよりも遅くなってしまいます。以下の例を見てみましょう。

myFunction(item) for item in array

確かに読みやすく簡潔な構文で、素晴らしいことに裏側で for ループにコンパイルしてくれます。言い換えれば、CoffeeScriptは forEach() とほぼ同じ表現力を提供し、かつ速度や古いブラウザへの互換を気にせずによいということです。

Map

forEach() と同様に、クラシックな for ループに加えて、ES5では map() という、ネイティブなマップ機能が導入されます。しかし forEach() と同様にパフォーマンスの点では注意が必要です。

var result = []
for (var i=0; i < array.length; i++)
  result.push(array[i].name)

var result = array.map(function(item, i){
  return item.name;
});

構文の節で説明したように、CoffeeScriptの内包表記は map() と同様の機能を果たしています。配列を返すことを明示するために、内包表記は必ず括弧で囲わなくてはいけません。

result = (item.name for item in array)

Select

ES5には filter() が導入され、配列をフィルタリングをすることができます。

var result = []
for (var i=0; i < array.length; i++)
  if (array[i].name == "test")
    result.push(array[i])

result = array.filter(function(item, i){
  return item.name == "test"
});

CoffeeScriptの構文では、 when キーワードを用いて要素を比較する事が出来ます。実際に実行する関数はスコープ漏れや変数のコンフリクトを避けるために匿名関数(Anonymous Function)が用いられます。

result = (item for item in array when item.name is "test")

括弧を忘れてしまうと 結果は配列の最後の項目になるので忘れないように注意しましょう。CoffeeScriptの内包表現は非常に柔軟なので、以下のような場合も対応できます。

passed = []
failed = []
(if score > 60 then passed else failed).push score for score in [49, 58, 76, 82, 88, 90]

# Or
passed = (score for score in scores when score > 60)

If comprehensions get too long, you can split them onto multiple lines.

passed = []
failed = []
for score in [49, 58, 76, 82, 88, 90]
  (if score > 60 then passed else failed).push score

Includes

値が配列内にあるかどうかを確認する場合、通常(もちろんこの"通常"にはIEは含まれていません)は indexOf() を用いる事でチェックする事が出来ます。

var included = (array.indexOf("test") != -1)

CoffeeScript has a neat alternative to this which Pythonists may recognize, namely in.

included = "test" in array

どのようにコンパイルされているかというと、CoffeeScriptは Array.prototype.indexOf() を使って配列をチェックしています。しかし、この場合文字列に対しては使う事が出来ません。そのため文字列の場合は indexOf() を用いましょう

included = "a long test string".indexOf("test") isnt -1

より良い例は、-1 の比較をビット演算子をつかって代替する方法です。

string   = "a long test string"
included = !!~ string.indexOf "test"

Property iteration

To iterate over a bunch of properties in JavaScript, you'd use the in operator, for example:

var object = {one: 1, two: 2}
for(var key in object) alert(key + " = " + object[key])

However, as you've seen in the previous section, CoffeeScript has already reserved in for use with arrays. Instead, the operator has been renamed of, and can be used like thus:

object = {one: 1, two: 2}
alert("#{key} = #{value}") for key, value of object

As you can see, you can specify variables for both the property name, and its value; rather convenient.

Min/Max

このテクニックは、CoffeeScriptに固有のものではありませんが、通常 Math.maxMath.min は複数の引数を取るのですが、... を最後に付けることで、配列を引数として処理する事ができます。

Math.max [14, 35, -7, 46, 98]... # 98
Math.min [14, 35, -7, 46, 98]... # -7

It's worth noting that this trick will fail with really large arrays as browsers have a limitation on the amount of arguments you can pass to functions.

Multiple arguments

In the Math.max example above, we're using ... to de-structure the array and passing it as multiple arguments to max. Behind the scenes, CoffeeScript is converting the function call to use apply(), ensuring the array is passed as multiple arguments to max. We can use this feature in other ways too, such as proxying function calls:

Log =
  log: ->
    console?.log(arguments...)

Or you can alter the arguments before they're passed onwards:

Log =
  logPrefix: "(App)"

  log: (args...) ->
    args.unshift(@logPrefix) if @logPrefix
    console?.log(args...)

Bear in mind though, that CoffeeScript will automatically set the function invocation context to the object the function is being invoked on. In the example above, that would be console. If you want to set the context specifically, then you'll need to call apply() manually.

And/or

CoffeeScript style guides indicates that or is preferred over ||, and and is preferred over &&. I can see why, as the former is somewhat more readable. Nevertheless, the two styles have identical results.

This preference over more English style code also applies to using is over == and isnt over !=.

string = "migrating coconuts"
string == string # true
string is string # true

CoffeeScriptでサポートされているとても便利な構文として、 or equals があり、Rubyの ||= に相当します。

hash or= {}
<<<<<<< HEAD

ハッシュが false だと評価された場合、それは空のオブジェクトだということになります。[]"" さらには 0falseとして評価されることに気をつけなくてはいけません。もしそれを意図してない場合は、hashundefined または null 場合にのみアクティブになるCoffeeScriptの存在確認演算子を使いましょう。

=======

If hash evaluates to false, then it's set to an empty object. It's important to note here that this expression also recognizes 0, "" and null as false. If that isn't your intention, you'll need to use CoffeeScript's existential operator, which only gets activated if hash is undefined or null:

>>>>>>> upstream/master

hash ?= {}

Destructuring assignments

深くネストされたオブジェクトや配列のプロパティも以下のようにして簡単にアクセスすることが出来ます。

someObject = { a: 'value for a', b: 'value for b' }
{ a, b } = someObject
console.log "a is '#{a}', b is '#{b}'"
<<<<<<< HEAD

External Libraries

=======

This is especially useful in Node applications when requiring modules:

{join, resolve} = require('path')

join('/Users', 'Alex')

External libraries

>>>>>>> upstream/master

最終的には結局JavaScriptにコンパイルされるので、外部ライブラリを使用する場合もCoffeeScriptのライブラリを呼び出すのと全く同じです。 jQueryをCoffeeScriptで使うときは、jQueryのAPIのコールバックのパターンをよりエレガントに書く事が出来ます。

# Use local alias
$ = jQuery

$ ->
  # DOMContentLoaded
  $(".el").click ->
    alert("Clicked!")

CoffeeScriptからコンパイルされたのコードはすべて、無名関数内にラップされているので、jQueryのためのエイリアスである$をローカルにセットして使うことが出来ます。

Private variables

The do keyword in CoffeeScript lets us execute functions immediately, a great way of encapsulating scope & protecting variables. In the example below, we're defining a variable classToType in the context of an anonymous function which's immediately called by do. That anonymous function returns a second anonymous function, which will be ultimate value of type. Since classToType is defined in a context that no reference is kept to, it can't be accessed outside that scope.

# Execute function immediately
type = do ->
  classToType = {}
  for name in "Boolean Number String Function Array Date RegExp Undefined Null".split(" ")
    classToType["[object " + name + "]"] = name.toLowerCase()

  # Return a function
  (obj) ->
    strType = Object::toString.call(obj)
    classToType[strType] or "object"

In other words, classToType is completely private, and can never again be referenced outside the executing anonymous function. This pattern is a great way of encapsulating scope and hiding variables.