※ 「あなたがRails触る人なら見ておきたい「体系的な」豆知識」からの派生記事です。
事前知識
- まずJSの知識が必須となるのでここら辺を参照されたい
CoffeeScriptとは
- 公式サイト -> CoffeeScript.org
- JavaScriptをRubyのような記法で記述することができる
- JSに比べ構成をシンプルにしたり記述量を大幅に減らすことができる
- 公式サイト内「 TRY COFFEESCRIPT 」でリアルタイムでJS変換しながらCoffeeScriptの動作を確認できる
- 実際にアプリケーションで使用する際はコンパイラを使用しJSに変換する
コメントアウト
単一行
- パウンド記号(
#
)以降がコメントになる - JSに変換されない
coffee
# comment
js
複数行
- パウンド記号×3(
###
)で囲んだ中がコメントになる - JSに変換されコメントとして残る
coffee
###
comment
comment
comment
###
js
/*
comment
comment
comment
*/
JSとの違い
-
var
宣言が不要 - 文末のセミコロン(
;
)が不要 - ブロックを囲む波括弧(
{}
)はインデントで代替 - あいまい性が無い場合は丸括弧(
()
)を省略可能
coffee
number = 52
if number > 30
alert "HelloWolrd!"
js
var number;
number = 52;
if (number > 30) {
alert("HelloWolrd!");
}
補足) JSをそのまま埋め込む場合
- バッククォート(
`
)で全体を囲む
coffee
`if (number > 30) {
alert("HelloWolrd!");
}`
js
if (number > 30) {
alert("HelloWolrd!");
};
文字列の扱い
式展開
- 文章を表すダブルクォーテーション(
"
)の中でパウンド+波括弧(#{}
)を用いると式展開を利用できる - 文章中で式展開を用いると変数やメソッドの使用が可能になる
coffee
num = 18
alert "doubled number is #{num * 2}"
js
var num;
num = 18;
alert("doubled number is " + (num * 2));
複数行
- 文章中の改行は単なるスペースとして認識される
coffee
intro = "
this
is
sample
message
"
js
intro = "this is sample message";
改行やスペースを保持した複数行
-
here document
と呼ばれる構文を用いる - ダブルクォーテーション×3(
"""
)で対象の文章を囲んであげる - 下記の例だと改行は全て
\n
に置換され保持されている
coffee
poem = """
I am a thousand winds that blow.
I am the diamond glints on snow.
I am the sunlight on ripened grain.
I am the gentle autumn's rain.
"""
js
var poem;
poem = "I am a thousand winds that blow.\nI am the diamond glints on snow.\nI am the sunlight on ripened grain.\nI am the gentle autumn's rain.";
配列の扱い
配列の生成
- Rubyで配列を生成する際と同様に配列を作成できる
- 項目を複数行に分ける場合にはインデントが必須
coffee
nums = [1, 2, 3, 4, 5]
words = [
"apple"
"orange"
"banana"
]
js
var nums, words;
nums = [1, 2, 3, 4, 5];
words = ["apple", "orange", "banana"];
範囲記法
- ピリオドを連続して記述することで範囲を示すことができる
- 数値の間にピリオド×2(
..
)を挟むと「以上/以下」の範囲を表す - ピリオド×3(
...
)を挟むと「以上/未満」の範囲を表す
coffee
ary_a = [0..5]
ary_b = [1...6]
js
var ary_a, ary_b;
ary_a = [0, 1, 2, 3, 4, 5];
ary_b = [1, 2, 3, 4, 5];
- 範囲記法を用いると配列の中の要素を指定することもできる
- その場合の数値は配列の要素の順番を表す
- 内部的には
sliceメソッド
を使用していることになる
coffee
alert ary_b[1..4]
alert ary_a[1..]
js
alert(ary_b.slice(1, 5));
alert(ary_a.slice(1));
補足) 範囲記法中のマイナス記号
- 配列の「最後から◯番目」という指定にはマイナス記号(
-
)が使える
coffee
ary = [1,2,3,4,5,6,7]
alert ary[2..-2]
js
var ary;
ary = [1, 2, 3, 4, 5, 6, 7];
alert(ary.slice(2, -1));
#=> 3, 4, 5, 6
オブジェクトの生成
- 文頭/文末の波括弧(
{}
)を省略したハッシュ形式で書くことができる - 複雑な階層を形成するオブジェクトはインデントをつけたYAML形式でも記述可能
coffee
obj_hash = name: "tomato", color: "red"
obj_yaml =
name : "poteo"
score:
eng : 98
math: 60
bio : 75
major:
main: "Linguistics"
sub : "Commerce"
js
var obj_hash, obj_yaml;
obj_hash = {
name: "tomato",
color: "red"
};
obj_yaml = {
name: "poteo",
score: {
eng: 98,
math: 60,
bio: 75
},
major: {
main: "Linguistics",
sub: "Commerce"
}
};
条件分岐
通常の記法
- CoffeeScriptの省略規則に則って書く以外はJSと同じ
coffee
if num > 30
alert "access velified"
else if num < 0
alert "bad access"
else
alert "you have wrong number"
js
if (num > 30) {
alert("access velified");
} else if (num < 0) {
alert("bad access");
} else {
alert("you have wrong number");
}
省略記法
- 残念ながら三項演算子を使用することはできない
- 代わりに
if ~~ then ~~ else
を用いて一行で記述することができる
coffee
if old < 20 then alert "access denied" else alert "go ahead"
js
if (old < 20) {
alert("access denied");
} else {
alert("go ahead");
}
- 条件の後置も可能
coffee
alert "access denied" if old > 20
js
if (old > 20) {
alert("access denied");
}
補足) 二項演算子
- 2つの内どちらかの値を返す性質を持つ二項演算子は存在する
- 第一候補の値が
true
の場合はその値を、false
の場合は第二候補の値を返す
coffee
role = flag ? 1
alert "値は #{role} です。"
# これ以前に flag = 0 と定義されていた場合
#=> "値は 0 です。"
# これ以前にflagの値が定義されていなかった場合
#=> "値は 1 です。"
js
var role;
role = typeof flag !== "undefined" && flag !== null ? flag : 1;
alert("値は " + role + " です");
比較演算子
等号の変換
- 比較対象の値が同等かどうか判断する
==演算子
は全てより厳密な===演算子
に置換される - 同様に比較対象の値が等しく無いことを判断する
!=演算子
も全て!==演算子
に変換される
coffee
if num == 1
txt = "number is 1"
else if num != 2
txt = "number is neither 1 nor 2"
js
if (num === 1) {
txt = "number is 1";
} else if (num !== 2) {
txt = "number is neither 1 nor 2";
}
比較演算子の連結
- 比較演算子を用いて条件を複数設ける際には通常
&&演算子
を用いて条件文を繋げなければいけない - CoffeeScriptでは比較演算子を連結して記述することで自動で複数の条件文に分けかつ繋げてくれる
coffee
alert "no prob" if 0 < num < 1
js
if ((0 < num && num < 1)) {
alert("no prob");
}
演算子/真偽値のエイリアス
- 演算子/真偽値には文字列のエイリアスが存在するものがある
- 記号を用いる代わりに単語を用いることでより直感的な記述を実現できる
記号 | エイリアス | 意味 |
---|---|---|
=== | is | 等しい |
!== | isnt | 等しくない |
&& | and | かつ |
|| | or | または |
! | not | 否定 |
true | yes / on | 真 |
false | no / off | 偽 |
- 下記は記号を用いた場合と文字列を用いた場合の比較
- なおJSへの変換後の、記述に差異は無いため1パターンしか記載しない
coffee
# 記号
alert "Valid" if check == true && flag != false
# 文字列
alert "Valid" if check is yes and flag isnt off
js
if (check === true && flag !== false) {
alert("Valid");
}
存在検証
- 指定の要素が配列やオブジェクトの中に存在するか否かを検証することができる
- 配列に対する要素の存在検証には
in
を用いる
coffee
ary = ["apple", "banana", "chocolate"]
alert("OK") if "banana" in ary
js
var ary,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
ary = ["apple", "banana", "chocolate"];
if (indexOf.call(ary, "banana") >= 0) {
alert("OK");
}
- オブジェクトに対する要素の存在検証には
of
を用いる
coffee
obj = name: "tomato", color: "red"
alert("OK") if "color" of obj
js
var obj;
obj = {
name: "tomato",
color: "red"
};
if ("color" in obj) {
alert("OK");
}
補足) 存在演算子
- クエスチョンマーク(
?
)で表す - 指定の値がtrueかfalseかを判定する
- JSにおいてどのような値がfalseになるかは冒頭のJSのまとめを参照されたい
coffee
if flag? then alert "YES" else alert "NO"
# これ以前にflagが定義されていた場合
#=> "YES"
# これ以前にflagが定義されていなかった場合
#=> "NO"
js
if (typeof flag !== "undefined" && flag !== null) {
alert("YES");
} else {
alert("NO");
}
- オブジェクトのプロパティに対しても存在検証できる
coffee
fruit =
name : "pineapple"
color: "yellow"
grade:
taste : 70
flaver: 89
# 存在検証を用いない場合
alert fruit.size.first
#=> "TypeError: Cannot read property 'first' of undefined"
# 存在検証を用いる場合
alert fruit.size?.first
#=> undefined
js
// オブジェクトの定義は省略
// 存在検証を用いない場合
alert(fruit.size.first);
// 存在検証を用いる場合
alert((ref = fruit.size) != null ? ref.first : void 0);
- 同様にメソッドに対しても存在検証可能
coffee
fruit =
name : "blueberry"
color: "blue"
grade:
taste : 64
flaver: 81
# 存在検証を用いない場合
alert fruit.hello()
#=> "TypeError: fruit.hello is not a function"
# 存在検証を用いる場合
alert fruit.hello?()
#=> undefined
js
// オブジェクトの定義は省略
// 存在検証を用いない場合
alert(fruit.hello());
// 存在検証を用いる場合
alert(typeof fruit.hello === "function" ? fruit.hello() : void 0);
switch文
- 元々のswitch文は以下のようなもの
js
switch (signal) {
case "red":
alert("STOP");
break;
case "blue":
case "green":
alert("GO");
break;
case "yellow":
alert("CAUTION");
break;
default:
alert("wrong signal");
}
-
CoffeeScriptでswitch文を使用する際には標準の省略規則以外にも幾つかの決まりがある
- 条件を表す
case句
はwhen句
で代替 - 条件が複数ある場合にはカンマ(
,
)で繋げて記述できる - 各条件と処理ブロックは
then
で繋げられる - 各条件ごとの処理ブロックの終わりを示す
break
は省略可能 - 「その他」の条件を表す
default句
はelse句
で代替
- 条件を表す
coffee
switch signal
when "red" then alert "STOP"
when "blue", "green" then alert "GO"
when "yellow" then alert "CAUTION"
else alert "wrong signal"
js
switch (signal) {
case "red":
alert("STOP");
break;
case "blue":
case "green":
alert("GO");
break;
case "yellow":
alert("CAUTION");
break;
default:
alert("wrong signal");
}
イテレータの活用
while文
- 標準の省略規則に則る
coffee
i = 0
sum = 0
while i < 10
sum += i
i++
alert sum
#=> 45
js
var i, sum;
i = 0;
sum = 0;
while (i < 10) {
sum += i;
i++;
}
alert(sum);
for文
- 標準の省略規則に則る
- 処理ブロックが一行の場合条件は後置にできる
coffee
sum += i for i in [0..9]
alert sum
#=> 45
js
for (i = j = 0; j <= 9; i = ++j) {
sum += i;
}
alert(sum);
-
by
を使用すると指定した範囲において「いくつ置きで処理を行うか」を指定できる
coffee
sum += i for i in [0..9] by 2
alert sum
#=> 20
js
for (i = j = 0; j <= 9; i = j += 2) {
sum += i;
}
alert(sum);
- イテレータによる処理を変数に代入すると各回の最後に評価された値を配列にして返す
coffee
conseq = (sum += i for i in [0..9])
alert conseq
#=> 0,1,3,6,10,15,21,28,36,45
js
conseq = (function() {
var j, results;
results = [];
for (i = j = 0; j <= 9; i = ++j) {
results.push(sum += i);
}
return results;
})();
alert(conseq);
配列やオブジェクトの中身の扱い
配列編
- 配列の中身を順番に取り出す処理は以下のように記述する
- 配列各要素の順番を表す変数の有無は任意
- 条件の有無も任意
- 今回は処理内容が短いので
for文
を後置している
coffee
<各要素に対する処理> for <各要素に与える変数(val)>, <各要素の順番を表す変数(i)> in <扱う対象となる配列(ary)> when <条件>
js
for (i = j = 0, len = ary.length; j < len; i = ++j) {
val = ary[i];
if ( <条件> ) {
<各要素に対する処理>
}
}
- 上記の用法を利用すると具体的に以下のようになる
coffee
ary = ["strawbarry", "banana", "orange"]
alert "#{i}: #{fruit}" for fruit in ary when fruit isnt "banana"
#=> 0: strawberry
#=> 2: orange
js
ary = ["strawbarry", "banana", "orange"];
for (i = j = 0, len = ary.length; j < len; i = ++j) {
fruit = ary[i];
if (fruit !== "banana") {
alert(i + ": " + fruit);
}
}
オブジェクト編
- 基本的に配列の要素を扱う際と同様の手段を採る
- 配列とは違いオブジェクトは各要素の「キーを表す変数」と「バリューを表す変数」の2つを設定する
-
in
ではなくof
を使う
coffee
<各要素に対する処理> for <各要素のキーを表す変数(key)>, <各要素のバリューを表す変数(val)> of <扱う対象となるオブジェクト(obj)> when <条件>
js
for (key in obj) {
val = obj[key];
if ( <条件> ) {
<各要素に対する処理>
}
}
- 上記の用法を利用すると具体的に以下のようになる
coffee
fruits = apple: "red", banana: "yellow", melon: "green"
alert "#{name}-> #{color}" for name, color of fruits when color isnt "red"
js
fruits = {
apple: "red",
banana: "yellow",
melon: "green"
};
for (name in fruits) {
color = fruits[name];
if (color !== "red") {
alert(name + "-> " + color);
}
}
関数の扱い
関数の生成
- JSにおける関数の生成方法は主に2種類あった
js
// ①
function hello() {
return alert("hello");
};
// ②
var hello = function() {
return alert("hello");
};
- CoffeeScriptでは②の方法といくつかのルールに則って記述する
- 基本的には標準の省略規則に則る
-
function宣言
は->
で代替する - 引数がない場合には
()
を付けない - 引数がある場合には
->
の前に()
を用いて記述する -
return
は明示しなくとも最後に評価された値が返り値となる
coffee
hello = (name) -> alert "hello #{name}"
js
var hello;
hello = function(name) {
return alert("hello " + name);
};
関数の呼び出し
-
関数名+引数
の記述で呼び出すことができる - 引数がない場合には
()
をつける - 引数がある場合には
()
は不要でありそのまま引数を記述する
coffee
hello()
hello "tomato"
js
hello();
hello("tomato");
引数の初期値
- 引数には初期値を与えることができる
- 初期値を与えることで呼び出しの際に引数を指定しなくとも初期値が自動で代入される
- 引数に初期値を設定するには
(引数 = 初期値)
という形で記述する
coffee
hello = (name = "apple") -> alert "hello #{name}"
hello()
js
var hello;
hello = function(name) {
if (name == null) {
name = "apple";
}
return alert("hello " + name);
};
hello();
即時関数/匿名関数の利用
- JSには即時関数/匿名関数という概念が存在する
- 即時関数とは定義と同時に呼び出しを待たず実行される関数のこと
- 名前を付けない関数という意味で匿名関数とも呼ばれる
js
(function() {
return alert("hello");
})();
- CoffeeScriptで即時関数/匿名関数を利用する際には関数を表す
->
の前にdo
を付ける
coffee
do -> alert "nice to meet you"
js
(function() {
return alert("nice to meet you");
})();
分割代入
- 複数の値をいっぺんに代入できる方法がいくつかサポートされている
配列を用いた代入
- 配列に対して配列で値を付与すると各要素にいっぺんに値を代入できる
coffee
[a, b, c] = ["berry", "banana", "bee"]
alert a #=> "berry"
alert b #=> "banana"
alert c #=> "bee"
js
ref = ["berry", "banana", "bee"], a = ref[0], b = ref[1], c = ref[2];
値の入れ替え
- 値には変数も用いることができる
- 変数を使うことで複数の値の中身を入れ替えることができる
coffee
a = "1st var"
b = "2nd var"
alert a #=> "1st var"
alert b #=> "2nd var"
[a, b] = [b, a]
alert a #=> "2nd var"
alert b #=> "1st var"
js
var a, b, ref;
a = "first var";
b = "second var";
alert(a);
alert(b);
ref = [b, a], a = ref[0], b = ref[1];
alert(a);
alert(b);
関数を用いた代入
- 関数の処理ブロックを配列にすることで複数の返り値を配列で受け取れる
- それを用意した配列に対して代入するといっぺんに複数の値を代入できる
coffee
calc = (num) -> [num, num * 2, num ** 2]
alert calc 3 #=> [3,6,9]
[row_num, doubled, squared] = calc 3
alert row_num #=> 3
alert doubled #=> 6
alert squared #=> 9
js
// 以下alert文は割愛
calc = function(num) {
return [num, num * 2, Math.pow(num, 2)];
};
ref = calc(3), row_num = ref[0], doubled = ref[1], squared = ref[2];
オブジェクトを用いた代入
- オブジェクトのから指定のプロパティだけを取り出して個別の変数に入れる処理も可能
- 変数名をプロパティ名と一致させることで簡潔に記述できる
coffee
apple =
looks: "red"
price: 2
taste: "sour"
weigh: 250
{looks, weigh, typeof} = apple
alert looks #=> "red"
alert weigh #=> 250
alert typeof #=> undefined
js
// 以下alert文は省略
apple = {
looks: "red",
price: 2,
taste: "sour",
weigh: 250
};
looks = apple.looks, weigh = apple.weigh, typeof = apple.typeof;
classの扱い
- classの宣言やインスタンスの生成等は基本的に省略規則に則る
インスタンス初期化の処理
-
幾つか独自の手法を用いることも可能
-
インスタンスに対する初期化の動作は
constructorメソッド
として記述するcoffeeclass Fruit constructor: (name, color) -> this.name = name; this.color = color
-
this.
は@
で代替可能coffeeclass Fruit constructor: (name, color) -> @name = name; @color = color
-
constructor
中で仮引数名とプロパティ名が一致する場合には仮引数名に@
をつけることで代入の処理を省略できるcoffeeclass Fruit constructor: (@name, @color) ->
-
-
以上の用法を利用してインスタンスを生成する
coffee
class Fruit
constructor: (@name, @color) ->
banana = new Fruit "Banana", "Yellow"
alert banana.name #=> "Banana"
alert banana.color #=> "Yellow"
js
var Fruit, banana;
Fruit = (function() {
function Fruit(name, color) {
this.name = name;
this.color = color;
}
return Fruit;
})();
banana = new Fruit("Banana", "Yellow");
thisの省略
- インスタンス初期化の処理の項でも挙げたが
this.
は省略可能 - 通常のメソッドにおいても省略は同様に使用できる
coffee
class Fruit
constructor: (@name) ->
hello_world: -> alert "Hello #{@name}!"
banana = new Fruit "Banana"
banana.hello_world() #=> "Hello Banana!"
js
var Fruit, banana;
Fruit = (function() {
function Fruit(name) {
this.name = name;
}
Fruit.prototype.hello_world = function() {
return alert("Hello " + this.name + "!");
};
return Fruit;
})();
banana = new Fruit("Banana");
banana.hello_world();
classの継承
-
extends
を用いることでクラスの継承もシンプルに実現できる - クラスを継承すると親クラスのプロパティ・メソッド等引き継ぐことができる
coffee
class Fruit
constructor: (@name) ->
hello_world: -> alert "Hello #{@name}!"
class Apple extends Fruit
sungold = new Apple "SunGold"
alert sungold.name #=> "SunGold"
js
var Apple, Fruit, sungold,
extend = 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; },
hasProp = {}.hasOwnProperty;
Fruit = (function() {
function Fruit(name) {
this.name = name;
}
Fruit.prototype.hello_world = function() {
return alert("Hello " + this.name + "!");
};
return Fruit;
})();
Apple = (function(superClass) {
extend(Apple, superClass);
function Apple() {
return Apple.__super__.constructor.apply(this, arguments);
}
return Apple;
})(Fruit);
sungold = new Apple("SunGold");
alert(sungold.name);
メソッドのオーバーライド
- 継承した親クラスのメソッドをオーバーライド(上書き)することもできる
-
superメソッド
を使用すると親クラスの元の記述を呼び出すことができる
coffee
class Fruit
constructor: (@name) ->
hello_world: -> alert "Hello #{@name}!"
class Apple extends Fruit
hello_world: -> super(); alert "...said Bob."
sungold = new Apple "SunGold"
sungold.hello_world()
#=> "Hello SunGold!"
#=> "...said Bob."
js
var Apple, Fruit, sungold,
extend = 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; },
hasProp = {}.hasOwnProperty;
Fruit = (function() {
function Fruit(name) {
this.name = name;
}
Fruit.prototype.hello_world = function() {
return alert("Hello " + this.name + "!");
};
return Fruit;
})();
Apple = (function(superClass) {
extend(Apple, superClass);
function Apple() {
return Apple.__super__.constructor.apply(this, arguments);
}
Apple.prototype.hello_world = function() {
Apple.__super__.hello_world.call(this);
return alert("...said Bob.");
};
return Apple;
})(Fruit);
sungold = new Apple("SunGold");
sungold.hello_world();