この記事が対応するCoffeeScriptのバージョンは1.7です。
基本的には本家サイトCoffeeScriptの内容と同じです。わかりやすいように色々と私が勝手に再構築、追加してはありますが。(日本語訳と紹介されるとちょっと心外ですな(´Д⊂ヽ )
なお、JavaScriptの基礎知識を前提とし、JavaScriptから引き継ぎ重複する内容は解説されません。
あ。後、この記事の文章、CoffeeScriptコード、JavaScriptコードに関する著作権は放棄しています。パブリックドメインと言うそうです。文章、コード自由に引用改変公開しても問題ありません。
はじめに:CoffeeScriptのメリットとデメリット
一般に言われているCoffeeScriptを使うメリットデメリットを載せておきます。
ま、こんな論争気にせず、使ってみて気に入ったら使い続ければいいし、気に入らなかったらやめればいいだけだと私は思うんだけどね。
メリット:
JavaScriptに比べて簡潔に書ける
==か===かといった問題がない
クラスが使える
デメリット:
近い将来廃れる
ある程度の規模になるとデバッグがしにくい
目次
秀丸 強調定義ファイル
誰かがすばらしい定義ファイルを作ってくれる日を願って………
Theテキトーな自作定義ファイル。
COFFEE.hilight 強調定義ファイル
COFFEEs.hilight 複数コメント定義ファイル
COFFEEoutline.hilight 解析定義ファイル
これらのファイルをnodamushiの許可無く改変、再配布することを禁じません。
そのような事態が発覚した場合、配布人へ謝意を申し上げに参ります。
………ま、そんな希有な人はいないだろうけど。
インストール
1.node.jsのインストール
2.コマンドプロンプトでnodeとタイプし、node.jsが起動することを確認し、終了する
(動かない場合はPATHの確認)
3.コマンドプロンプトで「npm install -g coffee-script」と入力
4.coffeeとタイプして動作を確認。
(動かない場合はPATHの確認)
Macなどでも1と3の手順で良いはず。
コンパイル
alert "Hello"
上記の内容をtest.coffeeとして適当なフォルダに保存。
コマンドプロンプトを起動して、フォルダに移動し、
coffee -cb test.coffee
とタイプ。test.jsが同じディレクトリに作成されます。
coffeeコマンドのオプション-cはjsファイルとしてコンパイル結果を出力するという意味。
-bは全体を関数として囲わないようにします。
同じファイル名+jsという名前ではなく、別なディレクトリに別な名前で保存したかったら-oオプションを指定する。
オプション一覧
オプション | 効果 |
---|---|
-c --compile |
JavaScriptファイルとして結果を出力する。 ファイル名は元のcoffeescriptファイルと同名。以下の例はsrcフォルダ内にあるcoffeeファイルをコンパイルする。 coffee -c src/*.coffee |
-i --interactive |
REPLを起動。rlwrapとあわせると良い |
-o --output |
JavaScriptファイルの出力ディレクトリの指定。cオプションやwatchとあわせて使う。以下の例はsrcフォルダ内にあるファイルをまとめてlibフォルダに出力する。 coffee -o lib/ -c src/ |
-j --join |
コンパイル前に、全てのスクリプトを渡された順番に結合する。 大きなプロジェクトに使うと便利だそうです。 バージョン1.1.0から結合後のファイル名を指定できるようになりました。 例)coffee -j out.js -c test.coffee test2.coffee |
-w --watch |
coffeeスクリプトの修正時間を監視し、変更されたらコンパイルし直す |
-p |
出力結果を標準出力にプリントする。 |
-l --lint |
JavaScript Lintがインストールされている場合、コンパイル結果をjslを使ってチェックする。 wオプションと伏せて使うといいらしい。 |
-s --stdio |
標準入力からcoffeeスクリプトを受け取り、標準出力へコンパイル結果を出力する。 |
-e --eval |
コマンドラインから直接入力されたCoffeeScriptをコンパイルして実行し、結果を表示する coffee -e "puts num for num in [10..1]" |
-r --require |
スクリプトをコンパイルまたは実行する前にライブラリーを読み込む。 |
-b --bare |
セーフティ関数で囲わない。 簡単に言えば、大域環境に登録するようになる。 |
-t --tokens |
CoffeeScriptをパースする代わりに、Lexで字句解析し、トークンストリームを出力。 |
-n --nodes |
CoffeeScriptをパースする代わりに、Lexで字句解析し、構文木を出力。 |
--nodejs | 何か--debugや--max-stack-sizeとかのフラグでNodejsと連携できるらしいよ。 誰か詳細求む |
インデント
pythonライクな言語なので、インデントの深さでブロックが生成されます。
処理群1 処理群2 処理群3 処理群4
こんな感じのインデントになっていたらJavaScriptでは
処理群1 { 処理群2 { 処理群3 } 処理群4 }
このような関係になる、という大雑把なイメージを持っておいてください。
変数
CoffeeScriptには変数宣言はありません。
変数名=………
の様に初めて記述したところから変数が宣言された物と見なされます。
@修飾子
CoffeeScriptでは変数名の前に@を付けた場合、現在のオブジェクトが保有する変数を指すようになります。
(だいたいthis.がついたのと同じ扱いになります。)
@param=1 ↓ this.param=1
ただし、後述する関数バインドなどでは、@が単純にthisに変換されない場合もあります。
一応、thisキーワードを使うことも可能ですが、@を使っておきましょう。なお、例えば関数チェーンなどで自分を返したいときに、@単体で使うこともできます。
数値
JavaScriptでは数値の書き方として
- 10進数(例:11)
- 8進数(例:013)
- 16進数(例:0xb)
があります。CoffeeScriptではv1.1.3からこれらに加え2進数表記が可能になりました。
0b〜
の形で表記します。以下の例は全て変数に11を代入しています。
a=11 b=013 c=0xb d=0b1011
文字列
文字列
文字列はダブルクォーテーション"とシングルクォーテーション'のどちらかで囲います。文字列は、下記例の様に複数行にわたって文を書けます。
改行は文字列に適応されませんが、バージョン1.7から半角空白が挿入されるようになりました。
また、行頭行末の空白は無視されます。
↓コンパイル
multistring="複数行文字列は
改行すると空白が入ります
←この空白やこの空白は→
無視されるンゴ
"
multistring='
←全角空白ですが→
無視されるンゴ
'
multistring = "複数行文字列は 改行すると空白が入ります ←この空白やこの空白は→ 無視されるンゴ"; multistring = '←全角空白ですが→ 無視されるンゴ';
シングルとダブルの違いは式展開において違いが出ます。
式展開(String Interpolation)
ダブルクォーテーション内部では以下の様に書くと式の展開が行われます。
#{式}
シングルクォーテーションでは式は展開されません。
式には変数名や、1+2などの数式を入れられます。
"A picture is a fact. -- #{ author }" ↓コンパイル "A picture is a fact. -- " + author;
サンプル
コンパイル結果。
sogeb='''
ヘ(^o^)ヘ いいぜ
|∧
/ /
(^o^)/ てめえが何でも
/( ) 思い通りに出来るってなら
(^o^) 三 / / >
\ (\\ 三
(/o^) < \ 三
( /
/ く まずはそのふざけた
幻想をぶち殺す
'''
cat="
吾輩は
猫である
気がする
"
alert "cat=#{cat}\nsogeb=#{sogeb}"
alert 'cat=#{cat}\nsogeb=#{sogeb}'
var cat, sogeb; sogeb = ' ヘ(^o^)ヘ いいぜ\n |∧ \n / /\n (^o^)/ てめえが何でも \n /( ) 思い通りに出来るってなら\n (^o^) 三 / / >\n\ (\\ 三\n(/o^) < \ 三 \n( /\n/ く まずはそのふざけた\n 幻想をぶち殺す'; cat = "吾輩は 猫である 気がする"; alert("cat=" + cat + "\nsogeb=" + sogeb); alert('cat=#{cat}\nsogeb=#{sogeb}');
配列
連番の配列を作成
範囲の指定によりaからb未満(もしくはbより大)の連続する値を持つ配列を簡単に生成できます。
[a...b]
「..」と2点の場合はaからb以下(もしくはb以上)の連続する値を持つ配列になります。
for文を利用した配列生成
for文(後述)を利用すると[1,3,5,7………101]といった配列も容易に生成できます。
arr = (num for num in [1...102] by 2)
arr=[1,2,3,4,5] arr=[1 2 3] arr=[1...10] arr=[12..10] arr=(num for num in [1...102] by 2)
コンパイル結果
var arr; arr = [1, 2, 3, 4, 5]; arr = [1, 2, 3]; arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr = [12, 11,10]; arr = (function() { var _results; _results = []; for (num = 1; num < 102; num += 2) { _results.push(num); } return _results; })();
配列の範囲内の要素を取り出す
配列ARRの添え字がaからbまでの要素を持つ配列arrなどを簡単に作成できます。
arr = ARR[a..b] #a以上b以下の要素の取り出し
arr = ARR[a...b] #a以上b「未満」の要素の取り出し
arr = ARR[a..]#a以上の要素の取り出し
arr = ARR[..]#クローンの作成(ver1.3.1以降)
コンパイルすると
var arr; arr = ARR.slice(a, (b+1) || 9e9); arr=ARR.slice(a,b); arr=ARR.slice(a); arr=ARR.slice(0);
配列の範囲内の要素を置換する
[a..b]の記法は取り出す以外にもその範囲の要素に値を代入することも出来ます。
a=[1..10]
a[2..3]=[10,11] #要素2が10に、要素3が11になる
a[4...6]=[10,11] #要素4が10に、要素5が11になる
分割代入
Perlを知っている方なら関数の引数を受け取るときに
($a,$b,$c)=@_
という風に、左辺の配列に入れた変数に右辺の配列の値を順に書き込めることをご存じかと思います。CoffeeScriptでも同じ事が出来ます。
コンパイル
[a,b,c]=[10,20,30] #変数aに10,変数bに20、変数cに30を代入
var a, b, c, _ref; _ref = [10, 20, 30], a = _ref[0], b = _ref[1], c = _ref[2];
また、左辺の配列に入れる変数の一つだけ「...」を後ろにくっつけることが出来ます。
これを付けた変数は適切な長さを持つ配列が代入されます。
[a,b,c...,d]=[1...10]
コンパイル
var a, b, c, d, _i, _ref; var __slice = Array.prototype.slice; _ref = [1, 2, 3, 4, 5, 6, 7, 8, 9], a = _ref[0], b = _ref[1], c = 4 <= _ref.length ? __slice.call(_ref, 2, _i = _ref.length - 1) : (_i = 2, []), d = _ref[_i++];
これらは以下のように代入する値に配列の範囲指定や、深いネスト構造をしていてもきちんと代入がなされます
x = [11..20] [ a, x[1...3], b ] = [1..10] [ a, [ b, c...], [d], e] = [ 1, [1...10], [10..20], 5]
また、上のコンパイル結果を見ても分かるようにいったん_refという名前の変数に配列を入れてからa,b,cに値をコピーしているので、以下のような二つの値の交換も安全に実行できます。
a=10 b=0 [a,b] = [b,a]
コンパイル
var a, b, _ref; a = 10; b = 0; _ref = [b, a], a = _ref[0], b = _ref[1];
オブジェクト(連想配列)
JavaScriptとほぼ同様です。{}は省略できます。「,」を記入せずにインデントを利用して作成することも出来ます。
コンパイル
obj={test:"ok",test2:"false"}
obj=
test:"ok"
test2:"false"
var obj; obj = { test: "ok", test2: "false" }; obj = { test: "ok", test2: "false" };
要素にはJavaScript同様にobj.testやobj["test"]でアクセスできます。
分割代入
連想配列でも配列の時のような分割代入が行えます。
連想配列のキーと同じ名前の変数に同じ名前のキーの値を代入することができます。
「キー名:変数名」を指定すると、そのキーに対応する要素が変数名に格納されます。
配列の時同様に深いネスト関係にあっても取得可能です。
コンパイル
object=
name: "nodamushi"
age : "19++++++++"
sex : "♂"
phone:
tell:"090-9876-5432"
{age,name}=object #nameとageを取得
{sex:seibetsu}=object #seibetsu変数にsexを代入
{phone : {tell}}=object #phoneのオブジェクト内にあるtellを取得。
var age, name, object, seibetsu, tell; object = { name: "nodamushi", age: "19++++++++", sex: "♂", phone: { tell: "090-9876-5432" } }; age = object.age, name = object.name; seibetsu = object.sex; tell = object.phone.tell;
演算子など
CoffeeScript固有の演算子等について
CoffeeScript | JavaScript |
---|---|
is,== | === |
isnt,!= | !== |
not,! | ! |
and,&& | && |
or,|| | || |
true, yes, on | true |
false, no, off | false |
@, this | this |
of | in |
in | no JS equivalent |
? | no JS equivalent |
** | Math.pow(a,b) |
// | Math.floor(a / b) |
%% | (a % b + b) % b |
JavaScriptの比較演算子「==」に対応する演算子は無いようです。
or=,||=,and=,&&=
普通「a += b」と記述することは「a = a + (b)」という処理と同じ事になりますが、or=,||=,and=,&&=といった比較演算子の場合以下のように展開されます。
a or= b ↓ a or (a=b)
aにa or bの値が入るのではなくaにはbが入るので注意してください。
存在演算子 ?
条件式の?
?という演算子は存在しているか(nullやundefinedでないか)を返す演算子です。
if a? #変数aが存在しているなら続く処理を行うという意味になる
これだけだと単にnullやundefinedの時falseでそれ以外ならtrueを返すように聞こえますが、そのほかの機能も有したマルチな演算子です。
二項演算子としての?
a?b
二項演算子として?を使うと上の式は次のような意味になります。
aが存在しているならばaを、そうでなければbを返す
a = b ? 1 #bがあればaにbを、そうでなければaに1を
a ?= 1 #aが無ければaに1を代入する。
?.アクセス演算子
?.演算子はセーフなアクセス演算子です。
JavaScriptは実行してみないと、アクセスする要素が存在しているかどうか分かりません。
それが問題となるとき?.アクセス演算子が役に立ちます。
a?.bはaが存在するならばaのbにアクセスする、という意味になります。udefinedの要素にアクセスするエラーが発生しません。
?.の形には.の代わりに関数の引数である()を入れた?()という形もあります。
コンパイル
obj={a:()->ob={tset:{target:"ok"}}}
target=obj.a?().test?.target;
#testはないのでundefindeが返ります(あるのはtsetなの)
var obj, target, _ref; obj = { a: function() { var ob; return ob = { tset: { target: "ok" } }; } }; target = typeof obj.a === "function" ? (_ref = obj.a().test) != null ? _ref.target : void 0 : void 0;
条件分岐
if,unless,else
ifは渡された条件式が真の時該当する処理を実行する。
unlessは渡された条件式が偽の時該当する処理を実行する。
elseはifもしくはunlessが成立しなかったときに実行される。
if文、unless文は条件式との区別がつかないので、条件式の後に実行したい処理を記述することは出来ない。
else文に関しては同じ行に実行する処理を書いても良い。(ただし、その場合複数行にわたるブロックは生成できないので注意)
例
コンパイル
if !a?
alert 1
else unless a is b # else if a isnt bとおなじ
alert 2
if (!(typeof a != "undefined" && a !== null)) { alert(1); } else if (a !== b) { alert(2); }
処理が一行しかない時の簡易記法
上の例のように処理が一行しかないときはわざわざ改行して、インデントを下げて記述なんて面倒くさいですね。
簡単に書く方法があります。
- 処理と分岐条件の間にthenを入れる
- ifを後ろに書く。(※else文の追加は出来ない。)
if a
alert 1
↓簡略化
if a then alert 1
alert 1 if a
3項演算子のif
if 〜 then 〜 elseの形は3項演算子としても使えます
a = if b then 1 else 2
JavaScriptで一般的な?を使った三項演算子は、?を存在演算子にしてしまった為使えません。
switch文
CoffeeScriptにおけるswitchでの分岐はcase、defaultではなく、whenとelseで書きます。
when節で処理が一行のみで多段組にしたくないときはthenを使って一行にまとめられます。
case 1: case 2: 処理
JavaScriptでは上のように書き並べるところは
when 1,2
と,区切りで書き並べます。
例
switch val
when 1
………
when 2 then ………
when 3,4,5 #いくつかをまとめたいときは,区切り
………
else
………
ループ
while,until
CoffeeScriptでは単純なループはwhileを使います。
while 条件式 処理
ここでも
処理 while 条件式
という簡略形の構文が使えます。
また、whileではJavaScript同様にbreakやcontinue文などの制御構文が使えます。
while not(条件式)と同じ意味になるuntilというループ構文もあります。
個人的な感想だけど、untilとかunlessはPerlで結局全然使わなかったなぁ………
whileは配列を返す
whileループはある種一つの関数の様なものとして扱われ、その返値は配列になります。
以下の例を見てみましょう
f=-> t = 10 while t-=1 t*2
(※ ->は無引数の関数を定義するという意味です。)
一見何もしない関数を定義したようですが、これをコンパイルするとこうなります。
var f; f = function() { var t, _results; t = 10; _results = []; while (t -= 1) { _results.push(t * 2); } return _results; };
このように_resultsという配列を返しています。
whileで代入される配列の要素は処理の一番最後の結果になります。
このためwhile文で終わる関数は必ず配列を返すようになります。while文で終わるが、本当は何も返さない関数の最後には明示的にnullを書かなければなりません
ただし以下のような場合は、配列を返す理由がない(受取手がいない)ので配列を返しません。
t=10 while t-=1 t*2
var t; t = 10; while (t -= 1) { t * 2; }
配列内包 for
配列内包とは
CoffeeScriptにおけるfor文は配列内包という、配列の要素を順に取り出していって各要素について何らかの操作をし、新たな配列に格納するという、配列と連想配列操作の構文になっています。
map関数と言ったら理解が早いかもしれません。
ただし、while同様コンパイラが新たな配列に格納する必要がないと判断できる場合(返ってくる配列の受け取り手が以内場合)は新たな配列を作成しません。
for文の構文
for文は配列と連想配列に対して使うことが出来ます。
新たな配列に格納する値は一回の処理で一番最後に行われる処理の返値や変数になります。
1.配列に対しfor文を使う場合
for 配列の要素を代入する変数 in 配列
処理
処理が一行だけの時は以下のように簡略化して書くことも出来る。
処理 for 配列の要素を代入する変数 in 配列
2.連想配列に対しfor文を使う場合
for キーを代入する変数,要素を代入する変数 of 連想配列
処理
なお、要素を代入する変数は省略することが出来ます。
処理が一行だけの時は以下のように簡略化して書くことも出来る。
処理 for キーを代入する変数,要素を代入する変数 of 連想配列
tips:インデックス変数は各ループ毎にイミュータブル
バージョンが1.3.1からはインデックスの変数を変更してもループに影響が出ません。私の語学力では言葉で説明しにくいので、以下のソースコードを見て貰った方が早いでしょう。
ループの処理の中でiの値を書き換えています。これをコンパイルすると以下の様になります。
for i in [0...10]
i=i*i
var i, _i; for (i = _i = 0; _i < 10; i = ++_i) { i = i * i; }
この様に実際にループの終了判定は変数_iを用いているので、iの値を処理中に書き換えても、各ループ開始時のiの値は順に0から9まで増えていきます。
tips:配列にアクセスするための値を格納する変数を決める
for文で配列の要素にアクセスするためのキー(数値)を格納する変数は_iなどコンパイラ側が用意してくれますが、以下の様にして自分で定義することが可能です。
この様に、配列の要素を代入する変数の次にもう一つ変数を宣言すると、arrにアクセスするための変数がnumberになります。コンパイルすると
arr = ['a','b','c']
for str ,number in arr
for (number =0, _len=arr.length; number < _len; number++){ str = arr[number];
といった感じになります。
これを利用すれば、今何度目のループか?なども取得できるでしょう。
by
for文は配列の要素を順に取り出していくが、飛び飛びに取り出していくこともできる。
そのための構文がbyだ
デフォルトではbyの次の要素までの距離が1である。
for 変数名 in 配列 by 次の要素までの距離
たとえば、2ずつ配列の要素をとっていきたいなら
for t in arr by 2
となるし、要素の値分飛ばしたいなら
for t in arr by t
となる。
例:
#数列1,3,5,7,9の総和の数列を作成する。
k=0
sums = k+=num for num in [1..10] by 2
#sums=1,4,9,16,25となる#値が"ok"のkeyだけ3つ返す
hash= java:"ok", ruby:"no", perl :"ok", javascirp :"ok",python:"ok"
k=0
oks = for language, value of hash
if "ok" isnt value then continue
break if k is 3
k++
languege
関数
関数呼び出し
関数の呼び出しは基本的には括弧はいらない
関数名 引数,引数………
が、関数に引数が無いときには括弧が必要である。
また、二つの関数の結果を加えたいとき
関数名 引数 + 関数名 引数
などの様に書くと
関数名(引数+関数名(引数))
の様に扱われてしまうので、括弧を付ける。なお、関数名と括弧の間にスペースを入れてはいけない。
関数名(引数) + 関数名 引数
サンプル
sqr=Math.sqrt square=(x)->x*x hypot=(x=0,y=0)-> temp=square(x) + square y sqr temp add=(x,y...)-> x+=temp for temp in y x add 1,hypot(1,2),hypot 2,3
コンパイル結果
var add, hypot, sqr, square; var __slice = Array.prototype.slice; sqr = Math.sqrt; square = function(x) { return x * x; }; hypot = function(x, y) { var temp; if (x == null) { x = 0; } if (y == null) { y = 0; } temp = square(x) + square(y); return sqr(temp); }; add = function() { var temp, x, y, _i, _len; x = arguments[0], y = 2 <= arguments.length ? __slice.call(arguments, 1) : []; for (_i = 0, _len = y.length; _i < _len; _i++) { temp = y[_i]; x += temp; } return x; }; add(1, hypot(1, 2), hypot(2, 3));
tips:変数名の衝突
スコープはRubyと同じらしい。(私実はRuby使えないけど。)
上サンプルコードでは、関数内部で使っているtempという変数がグローバル領域で定義されていないので、コンパイル後は関数内部でvar tempと変数宣言されています。しかし、この関数よりも前にグローバル領域に変数tempが宣言されている場合、コンパイラはグローバル変数tempと関数内部のtempを同じ物と見なしてしまい、ローカル変数宣言は行われません。
ローカル変数かどうかをCoffeeScriptだけで指定する方法はないらしい*2ので名前の衝突が起こらないようにしなければならない。
関数をjs風に書いてバッククォーテーションで囲むとそのまま出力されるので、どうしてもローカル変数を宣言したいならこれを使うしかない模様。バージョンが1.3.1以降ならば、do関数を利用することも考えられると思います。(関数にダミー引数を入れて、それをローカル変数として利用する方法も考えられます。しかし、可変長因数が使えなくなりますし、ソースコードの可読性が下がってしまうので、止めた方が良いと思います)
do関数
JavaScriptではループの中で、変数を保護するためにクロージャーを使うことが一般的なんだそうです。へー、知らなかった。
さて、「do」というキーワードを用いて関数を定義すると、定義をしただけで自動的に実行をしてくれます。
このソースコードをコンパイルすると、
for i in [0..9]
do(i)->
alert(i*i)
var i, _fn, _i; _fn = function(i) {return alert(i * i);}; for (i = _i = 0; _i <= 9; i = ++_i) { _fn(i); }
この様に自動的に作成した関数を呼び出してくれています。
また、バージョン1.3.1からdo関数のネームスペースが柔軟になりました。
次のCoffeeScriptを考えます。このdo関数はxの初期値としてobj要素が"obj"というオブジェクトを与えています。
これを1.3.1以前のバージョンでコンパイルすると
x={obj:1}
do(x={obj:"obj"})->
alert(x.obj)
var x; x = { obj: 1}; (function(x) { if (x == null) { x = {obj: "obj"}; } return alert(x.obj); })(x);
この様になります。xはnullでないので、実行すると「1」がアラートされます。
次に1.3.1以降でコンパイルするとこの様になります。
var x; x = { obj: 1}; (function(x) { return alert(x.obj); })({ obj: "obj"});
この場合、実行すると"obj"がアラートされます。外側のxと内側のxは別物だと言うことをよりはっきりさせたと言うことなのでしょう。
関数バインディング
addEventListenerやjQueryのbindなどでイベントに関数を渡すとき、イベントに関数を渡した場所のオブジェクトの持つ変数vにアクセスしたくてうっかりthis.vなんてやって失敗しまくってないだろうか。(私だけ?)
CoffeeScriptではその煩わしさを解消してくれるすばらしい関数定義の方法がある。
()=> 処理
という構文だ。通常の関数定義が「->」に対して関数バインディングでは「=>」となっただけである。
この構文内ではthisや@修飾子が指すオブジェクトが関数を定義したオブジェクトになる。
サンプル
上の例では@parametaや@addは同じ物を指している。
func =->
@parameta="test"
@add =(t)->t+t
doc=document.getElementById("test")
doc.addEventListener 'click',(e)=>
doc.innerHTML = @add @parameta
alert(doc.innerHTML)
,false
コンパイルするとこのようになる。
var func; func = function() { var doc,_this = this; this.paraeta = "test"; this.add = function(t) { return t + t; }; doc = document.getElementById("test"); return doc.addEventListener('click', function(e) { doc.innerHTML = _this.add(_this.parameta); return alert(doc.innerHTML); }, false); };
addEventListenerに渡している無名関数内の@adや@parametaがthisではなく、_thisに変更されていることがわかる。
クラス
JavaScriptのクラスは使いにくい。特に継承が。(というか、実際はクラスないけど…)
super関数を呼び出すとか結構しんどいよね。
CoffeeScriptにはそんな問題を一気に解決してくれるすばらしいクラス構文があります。
prototypeメンバの追加
JavaScriptでFunc.prototype={………}と書いていた部分は以下の様に書きます
class クラス名 member : ……… member2:………
オブジェクト(連想配列)の構文にクラス宣言がついただけですね。
また、以下の様にも追加できます。
クラス名::member = ………
prototypeメンバへのアクセス
以下の様にアクセスできます。
クラス名::member = ………
クラス名::member()
インスタンス内からはJavaScriptでやるように、thisを使ってアクセスすると良いです。
thisの代わりに@修飾子を使うとタイプが楽です。
コンストラクター
クラスのインスタンスを生成する際に実行される関数、コンストラクターは以下の様に定義します。
class クラス名 constructor:(引数)-> 処理
クラス名::constructorで宣言してはいけません。
後から追加するとコンストラクターが呼ばれません。
tips:拡張したクラスのinstanceof
「インスタンス instanceof クラス名 」でインスタンスがクラスのインスタンスかどうかを調べられます。
このクラス名は拡張したクラス名でもきちんとtrueが返ってきます。
class Test
class Test2 extends Testt = new Test2
t instanceof Test2 #true
t instanceof Test #true
super
スーパークラスと同じ名前関数を定義することをオーバーライドといいます。
オーバーライドした関数からスーパークラスの関数をsuperで呼ぶことが出来ます。
例
class Person
constructor : (@name="",@age=0)->
say:()->"my name is #{@name}"class Nodamushi extends Person
constructor: ->super("nodamushi",19)
say:()-> super() + "I'm always #{@age}years old"noda = new Nodamushi
alert noda.say()
#↑my name is nodamushi I'm always 19 years oldとアラート
tips:スーパークラスがオーバーライドされた関数を呼ぶときは?
C++風に言うならば、CoffeeScriptのクラスの関数には全てにvirturalがついているような物です。
以下の様に定義されているとき、Testクラスの関数aはTest2クラスのインスタンスから呼ばれたとき、Test2クラスでオーバーライドした関数bを呼び出します。
class Test
a:->b()
b:->alert("ok")
class Test2 extends Test
b:->alert("(´・∀・`)") #オーバーライドt = new Test2
t.a() #(´・∀・`)がアラートされます。
tips:クラスの変数、関数
prototypeメンバではなくクラスに属する変数や関数を定義するには以下の様にする。
コンパイル結果
class Test
@test: 1
@func: ->
var Test; Test = (function() { function Test() {} Test.test = 1; Test.func = function() {}; return Test; })();
tips:クラスの関数のオーバーライドについて
v1.1.3からクラスに属する関数をオーバーライドしたときにsuperで呼び出すことが可能になりました。
コンパイルすると
class T
@f:->alert("a")class T2 extends T
@f:->super#v1.1.3からは「a」がアラートされる
T2.f()
と「constructor」(すなわちfunction T)が入るようになり、呼び出せるようになっています。以前のバージョンだとconstructorが入りません。
T2.f = function() {
return T2.__super__.constructor.f.apply(this, arguments);
};
tips:private staticな変数、関数
Javaでいうクラスだけが使える変数や関数、すなわちprivate static〜みたいなのは以下の様に定義すればいいようだ。
コンパイル結果
class Test
privatevalue=1
privatefunc=(v)->
alert v
a:->privatefunc(privatevalue)
#@やらthisはいらない。
Test = (function() { var privatefunc, privatevalue; function Test() {} privatevalue = 1; privatefunc = function(v) { return alert(v); }; Test.prototype.a = function() { return privatefunc(privatevalue); }; return Test; })();
JavaScriptの挿入
バッククォーテーションでくくった部分はJavaScriptとして認識され、そのままjsファイルに書き込まれます。
` var k=12; function test(){return "ok";} `
コンパイル結果
var k=12; function test(){return "ok";} ;
これを利用すると関数内でローカル変数を明示的に宣言することが出来ます
k=12 f=()-> `var k;` k=5 f() alert k
コンパイル結果
var f, k; k = 12; f = function() { var k;; return k = 5; }; f(); alert(k);
出力結果があんまり綺麗に整形されてないね………。
正規表現
複数行
///で囲むことで複数行にわたってパターンを記述できます。改行や空白は無視されます。
/// パ タ ー #半角空白またはタブを#の前に書けばコメントの記入も可 ン ///g ↓コンパイル /パターン/g
例外
throwも式
1.2.0からthrowそれ自身も式として扱われるようになりました。具体的には
こんなソースコードが通るようになりました。コンパイルすると以下の様になります。
a=throw("throw")
a = (function() { throw "throw"; })();
………で、これどこで使うんすか?
Cakefile
CoffeeScriptにはMakefileの様な機能を持った物を作る事が出来ます。
下記の記事で解説を書きました。
Cakefileの使い方
*1:Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements to CoffeeScript functions, they nonetheless return their final value.
*2:This behavior is effectively identical to Ruby's scope for local variables. Because you don't have direct access to the var keyword, it's impossible to shadow an outer variable on purpose, you may only refer to it. So be careful that you're not reusing the name of an external variable accidentally, if you're writing a deeply nested function.
だそうです。