今日から始めるCoffeeScript

こんにちは。毎年花粉症になりかけている飯塚です。

最近のNode.jsの普及などによってサーバサイドJavaScript界隈が盛り上がっています。 そんなホットなJavaScriptをラクにかつエレガントに書くためのCoffeeScriptという言語をチュートリアル風に紹介します。

何かしらのライブラリを自分で書く程度にJavaScriptで開発している人は絶対に使ったほうがいいと思います。 JavaScriptを知っていれば1-2時間程度で十分駆使できるようになります。

(2011/6/28:@m_satyr様にご指摘頂いた箇所を修正しました。)

目次


CoffeeScriptとは?

  • JavaScriptにコンパイルできる小さな言語
  • コンパイル後はJavaScriptとして動作するため実行速度面ではほぼ変わらない
  • 構文がRubyやPythonライク
  • ブラウザ用の開発にもサーバサイドの開発にも使える

作者はDocumentCloudのJeremy Ashkenas氏です。 CoffeeScriptはSCSSなどと一緒にRails 3.1で公式にサポートされる予定です。

利点

  • タイプ数が減る
  • すでにJavaScriptを習得していれば学習コストが低い
  • 既存のJavaScript資産をそのまま使える

CoffeeScriptのソースはJavaScriptに比べて見た目がすっきりするため、クラス数の多くなる大規模開発では特に有効です。

欠点

  • コンパイルが必要

インストール

Node.jsの実行環境とnpm(パッケージマネージャ)が必要なので、入っていない場合はまずインストールします。 インストール方法はこちらの記事などを参考にしてください。

もしインストールせずに試してみたい場合、CoffeeScriptのサイトに行き、上部のメニューから「TRY COFFEESCRIPT」をクリックするとインタプリタが使えます。左のスペースにCoffeeScriptを入力すると右のスペースにJavaScriptが出てきます。

npmがインストールできたら以下のコマンドでCoffeeScriptをインストールします。

$ npm install coffee-script

Hello World

まずは Hello World を作ります。

hello = ->
    console.log("Hello World!")

hello()

上記のソースコードをtest.coffeeというファイル名で保存します。

それを以下のようにcoffeeコマンドでJavaScriptにコンパイルします。

$ coffee -c test.coffee

同じディレクトリにtest.jsが生成されますので、これをNode.jsで実行します。

$ node test.js
Hello World

test.jsの中身を覗くと以下のようになっています。

(function() {
  var hello;
  hello = function() {
    return console.log("Hello World!");
  };
  hello();
}).call(this);

綺麗にJavaScriptに変換されています。

また、test.jsを作らずにcoffeeコマンドで直接実行することもできます。

$ coffee test.coffee
Hello World

構文

Hello Worldスクリプトから見て取れるように、CoffeeScriptには以下の特徴があります。

  • var での変数宣言が不要
  • 関数の定義(function)の代わりに -> を使う
  • 式の終わりや行末にセミコロンを必ずしも置かなくてもよい
  • インデントでブロックを表現する

引数を取る関数の場合、() を省略して書くこともできます。

console.log("Hello World!")

console.log "Hello World!"

これらのコンパイル結果はどちらも同じです。

coffeeコマンドには主に以下のようなオプションがあります。

-c (--compile)

.coffee から .js にコンパイルします。

-b (--bare)

外側の (function(){ ... }).call(this); を取り除きます。

-w (--watch)

ソースファイルを監視して、ファイルが変更されるたびにコンパイルを実行します。

オプションなしでcoffeeコマンドを起動した場合、インタラクティブなインタプリタが起動します。

関数

-> を使って定義します。引数を取る場合は ->(var1, var2) -> のようにして引数を並べます。 関数内の最後の値が自動的にreturnされます。

CoffeeScript:

square = (x) -> x * x

↓コンパイル結果

square = function(x) {
  return x * x;
};

複数行にわたる場合はインデントを使います。

fib = (n) ->
    if n < 2
        n
    else
        fib(n - 1) + fib(n - 2)

1行で書くと以下のようになります。

fib = (n) -> if n < 2 then n else fib(n - 1) + fib(n - 2)

↓いずれもコンパイル結果は全く同じです:

var fib;
fib = function(n) {
  if (n < 2) {
    return n;
  } else {
    return fib(n - 1) + fib(n - 2);
  }
};

変数展開

" " でくくった文字列の中では #{変数名} で変数の値を埋め込むことができます。

CoffeeScript:

name = "Nao"
console.log "My name is #{name}!"

↓コンパイル結果

var name;
name = "Nao";
console.log("My name is " + name + "!");

演算子

JavaScriptの == は自動的に型変換が行われるため、意図しない結果となる場合があります。 CoffeeScriptでは == または is と書くとJavaScriptの === に変換されるため、型も含めた厳密な比較となります。

他にも and, or などを使って読みやすく書くことができます。対応は以下の通りです。

CoffeeScript | JavaScript
-------------+-----------
      is     |    ===
    isnt     |    !==
     not     |      !
     and     |     &&
      or     |     ||

Objectの生成

Objectを作るときは以下の書式が使えます。JavaScriptの { } も使えます。

また this.x の代わりに @x と書くことができます。

CoffeeScript:

pos =
    x: 100
    y: 200
    dump: ->
        console.log "x:#{@x}, y:#{@y}"

↓コンパイル結果

var pos;
pos = {
  x: 100,
  y: 200,
  dump: function() {
    return console.log("x:" + this.x + ", y:" + this.y);
  }
};

1行でも書けます。

CoffeeScript:

size = width: 100, height:100

↓コンパイル結果:

var size;
size = {
  width: 100,
  height: 100
};

この書式は関数の引数にも応用できます。

CoffeeScript:

myFunc(width:100, height:100)

↓コンパイル結果:

myFunc({
  width: 100,
  height: 100
});

ループ

配列

for .. in でループが書けます。

arr = ["a", "b", "c", "d", "e"]
for val in arr
    console.log val

ループ中に配列のインデックスを拾いたい場合はforinの間に2つ変数を置きます。

arr = ["a", "b", "c", "d", "e"]
for val, i in arr
    console.log "#{i}: #{val}"

連想配列 (Object)

of を使って配列と同じように反復処理ができます。

data =
    x: 100
    y: 200
for name, value of data
    console.log "#{name}: #{value}"

存在チェック

変数名や関数名の直後に ? を付けると「その変数あるいは関数が定義されておりnull以外の値が入っているかどうか」をテストできます。

CoffeeScript:

if myName?
    console.log "yes"
else
    console.log "no"

↓コンパイル結果:

if (typeof myName != "undefined" && myName !== null) {
  console.log("yes");
} else {
  console.log("no");
}

また .() の前に ? を置くことで、変数や関数が存在する場合のみ処理を進めるようにできます。

CoffeeScript:

console?.log?("Hello World")

↓コンパイル結果:

if (typeof console != "undefined" && console !== null) {
  if (typeof console.log == "function") {
    console.log("Hello World");
  }
}

ヒアドキュメント

''' でヒアドキュメントが使えます。行頭からのインデント分は自動的に削られます。

html = '''
       <html>
       <head>
        <title>CoffeeScript</title>
       </head>
       <body>
        <table>
         <tr>
          <td></td>
         </tr>
        </table>
       </body>
       </html>
       '''

↓コンパイル結果

var html;
html = '<html>\n<head>\n <title>CoffeeScript</title>\n</head>\n<body>\n <table>\n  <tr>\n   <td></td>\n  </tr>\n </table>\n</body>\n</html>';

thisのバインド

関数定義時に -> の代わりに => を使うと、thisがバインドされて外側のthisを参照するようになります。

CoffeeScript:

pos =
    x: 100
    y: 200
    dump: ->
        # 関数内部で@x(this.x)を使いたいのでfuncの定義は=>にしないといけない
        func = =>
            console.log "x:#{@x}, y:#{@y}"
        func()

↓コンパイル結果:

var pos;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
pos = {
  x: 100,
  y: 200,
  dump: function() {
    var func;
    func = __bind(function() {
      return console.log("x:" + this.x + ", y:" + this.y);
    }, this);
    return func();
  }
};

クラス

以下のようにしてクラスを使うことができます。

class Animal
    # newした時に呼ばれるコンストラクタ
    constructor: (name) ->
        @name = name
    say: (word) ->
        console.log "#{@name} said: #{word}"

class Dog extends Animal
    constructor: (name) ->
        # 親クラスのコンストラクタを呼ぶ
        super name
    say: (word) ->
        # 親クラスのメソッドを呼ぶ
        super "Bowwow, #{word}"

dog = new Dog("Bob")
dog.say("Hello!")

>> Bob said: Bowwow, Hello! と出力される

クラスの静的なプロパティを定義するには変数名の頭に@を付けます。

CoffeeScript:

class Dog
    @TYPE_CHIHUAHUA = 1

↓コンパイル結果:

var Dog;
Dog = (function() {
  function Dog() {}
  Dog.TYPE_CHIHUAHUA = 1;
  return Dog;
})();

無名関数

setTimeoutなど引数に無名関数を渡したい場合は、次の行頭にカンマを付けます。

CoffeeScript:

setTimeout ->
    console.log("ok")
, 1000

コンパイル後のJavaScript:

setTimeout(function() {
  return console.log("ok");
}, 1000);

(2011/4/22 追記) 1行で書きたい場合は以下のようにします。

setTimeout (-> console.log("ok")), 1000

{var}

nameという変数がある場合、{name} と書くと {name:name} の意味になります。

また = の左辺に {var} を書く記法があり、require()の際に便利です。

例えば便利なライブラリの一つにUnderscore.jsがありますが、これを使う場合、CoffeeScriptのファイルとunderscore.jsが同じディレクトリにあれば以下のようにしてロードできます。

{_} = require './underscore'

これは以下のコードと同等です。

_ = require('./underscore')._

なおUnderscore.js 1.1.3以降では ._ を付ける必要がなくなったため、以下のように読み込むことができます。

_ = require './underscore'

他にもまだまだあります

ほかにも便利な構文がいろいろあります。詳細はCoffeeScriptのサイトを参照してください。


Tips

実際に自分で使っていて気づいたtipsを紹介します。

?の使い分け

? 記号は三項演算子ではないため、意図したコードになっているか注意してください。

変数名や関数名の直後に ? を置いた場合は存在チェックになります。

一方、変数の後にスペースを入れて?を置くと、変数が未定義かnullの場合に特定の値を代入する式が書けます。

CoffeeScript:

root = exports ? window

↓コンパイル結果:

var root;
root = typeof exports != "undefined" && exports !== null ? exports : window;

?= を使って、変数が未定義またはnullの場合だけ代入することもできます。

CoffeeScript:

console ?= {}

↓コンパイル結果:

typeof console != "undefined" && console !== null ? console : console = {};

従来の三項演算子の用途には ? は使えないため、代わりにif/then/elseを使います。

result = if isTrue? then "true" else "false"

存在チェック漏れ

CoffeeScript:

if opts.key?
    console.log "yes"

と書いた際、opts.keyの存在はチェックされますが、opts変数の存在自体はチェックされません。 もしoptsがundefinedやnullなら実行時にエラーが出てしまいます。

↓コンパイル結果:

if (opts.key != null) {
  console.log("yes");
}

これに対処してoptsの存在もチェックするには以下のように書きます。

CoffeeScript:

if opts?.key?
    console.log "yes"

↓コンパイル結果:

if ((typeof opts != "undefined" && opts !== null ? opts.key : void 0) != null) {
  console.log("yes");
}

for文でのreturn値

for文の値を以下のように受け取ると、配列のmapに相当する処理が簡潔に書けます。

CoffeeScript:

values = (num * 2 for num in [1..5])
console.log values

>> [ 2, 4, 6, 8, 10 ] と出力される

↓ コンパイル結果:

var num, values;
values = (function() {
  var _results;
  _results = [];
  for (num = 1; num <= 5; num++) {
    _results.push(num * 2);
  }
  return _results;
})();
console.log(values);

関数では最後の値が自動的にreturnされますが、returnを書かずにforを関数の最後に置いた場合、配列を作ってreturnしようとしてしまうので気をつけてください。

例えば以下のようなケースです。

CoffeeScript:

myFunc = ->
    for i in [0..10]
        something()

↓コンパイル結果:

var myFunc;
myFunc = function() {
  var i, _results;
  _results = [];
  for (i = 0; i <= 10; i++) {
    _results.push(something());
  }
  return _results;
};

_resultsが生成されてreturnされています。

これを防ぐには明示的にreturnを書きます。

CoffeeScript:

myFunc = ->
    for i in [0..10]
        something()
    return

↓compile結果:

var myFunc;
myFunc = function() {
  var i;
  for (i = 0; i <= 10; i++) {
    something();
  }
};

isを使うときの型に注意

JavaScriptの==では自動的に型変換が行われますが、isでは型もチェックされますので注意してください。

例:

if 4 is "4"
    console.log true
else
    console.log false

>> false が出力される

if `4 == "4"` # ``で囲うと直接JavaScriptを書けます。
    console.log true
else
    console.log false

>> true が出力される

-wオプションでのコンパイルエラー

-w オプションを付けてcoffeeコマンドを起動しているとき、ソースを修正したつもりでもコンパイルエラーになっていてjsファイルが更新されていない時がありますので気をつけましょう。

MacであればコンパイルエラーをGrowlで通知してくれるJitterというツールもあります。

CoffeeScriptの直接実行

実はコンパイルをしなくても、ライブラリを読み込むことでブラウザ上でCoffeeScriptのコードを実行できます。

以下のようにjsdo.itでもCoffeeScriptが使えます。ぜひ Fork ボタンを押していじってみてください!

CoffeeScript出来るかな - jsdo.it - share JavaScript, HTML5 and CSS

ただしオーバーヘッドなども出てきますので、本番環境ではJavaScriptにコンパイルしてデプロイするようにしてください。

おわりに

以上、長くなりましたがCoffeeScriptについて紹介させていただきました。

カヤックでは新しい技術に興味のあるあなたのようなエンジニアを求めています!

参考URL

CoffeeScript (http://jashkenas.github.com/coffee-script/)

Underscore.js (http://documentcloud.github.com/underscore/)

Node.js (http://nodejs.jp/nodejs.org_ja/)