書籍転載:Ruby on Rails 4アプリケーションプログラミング
CoffeeScript入門(後編) ― 関数/オブジェクト指向構文/即時関数
CoffeeScriptの基礎を解説。今回は関数/オブジェクト指向構文/即時関数について説明する(書籍転載の9本目)。CoffeeScriptをマスターしよう。
書籍転載について
ご注意
本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。
前回は、CoffeeScriptの基礎として基本構文/変数とリテラル表現/演算子/制御構文について解説しました。今回からは関数/オブジェクト指向構文/即時関数について説明します。
■
9.3.6 関数
関数は、「(引数) -> 式」の形式で表します。戻り値を返すためのreturn命令は必要ありません。CoffeeScriptでは、最後の式の値が自動的に戻り値となるためです*18。
# 引数height、weightからBMI(体格指数)を求めるbmi関数
bmi = (height, weight) ->
weight / (height * height)
# 関数の呼び出しにはカッコは不要(あってもOK)
alert bmi 1.75, 60
|
var bmi;
bmi = function(height, weight) {
return weight / (height * height);
};
alert(bmi(1.75, 60)); ……結果:19.591836734693878
|
- *18 条件分岐などがあっても、適切に最後の式が判定されます。
引数/戻り値がない場合
引数を受け取らない場合には「(引数)」の部分は省略できます。ただし、その場合は呼び出しのカッコは必須となりますので、注意してください(さもないと、関数呼び出しであることを認識できないからです)。
また、戻り値が不要である場合には、明示的に空のreturn命令を記述する必要があります。
nice = ->
alert 'Nice!'
return
nice() #……引数がない場合は呼び出しのカッコは必須
|
var nice;
nice = function() {
alert('Nice!');
};
nice();
|
太字の部分を削除した場合、以下のように、alertメソッドの戻り値がnice関数の戻り値となるようなJavaScriptが生成されます。この例に限っては、結果的に戻り値は変化しませんが*19、意図しない値が返るのを防ぐためにも、戻り値が不要である場合はreturn命令を明記するようにしてください。
nice = function() {
return alert('Nice!');
};
|
- *19 alertメソッドも戻り値はundefined(未定義)であるからです。
引数のデフォルト値
「引数名 = 値」の形式で、引数にデフォルト値を持たせることもできます。
# 引数typeがtriangleの場合は三角形の、それ以外は四角形の面積を求めるarea関数
# 引数typeのデフォルト値として'triangle'を設定
area = (base, height, type = 'triangle') ->
if type is 'triangle'
base * height / 2
else
base * height
alert area 3, 6, 'triangle'
|
var area;
area = function(base, height, type) {
if (type == null) { ……デフォルト値は、このように展開される
type = 'triangle';
}
if (type === 'triangle') {
return base * height / 2;
} else {
return base * height;
}
};
alert(area(3, 6, 'triangle')); ……結果:9
|
コンパイル済みのJavaScriptを見てもわかるように、引数のデフォルト値は内部的には引数がnullの場合にデフォルト値をセット、という形で実装されています(太字)。
可変長引数
引数の末尾に「...」を付与した場合には、可変長引数と見なされます。可変長引数は内部的には配列と見なされますので、for…in命令などで処理できます。
# 可変長引数numsの総積を求めるproduct関数
product = (nums...) ->
total = 1
# 可変長引数は配列としてそのまま処理できる
total *= num for num in nums
total
alert product 1, 2, 3
|
var product,
__slice = [].slice;
product = function() {
var num, nums, total, _i, _len;
nums = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
total = 1;
for (_i = 0, _len = nums.length; _i < _len; _i++) {
num = nums[_i];
total *= num;
}
return total;
};
alert(product(1, 2, 3)); ……結果:6
|
匿名関数
匿名関数(無名関数)も、変数への代入がないというだけで、同じ要領で記述できます。たとえば以下は、jQueryのhoverメソッドをCoffeeScriptから呼び出した例です。
# マウスポインタで出入りするタイミングでid="button"要素のsrc属性を変更
$ ->
$('#button').hover ->
$(this).attr 'src', 'enter.gif'
return
, ->
$(this).attr 'src', 'out.gif'
return
return
|
$(function() {
$('#button').hover(function() {
$(this).attr('src', 'enter.gif');
}, function() {
$(this).attr('src', 'out.gif');
});
});
|
9.3.7 オブジェクト指向構文
JavaScriptではいわゆるクラスという概念はなく、代わりにプロトタイプというクラスライクな概念がありましたが、考え方にやや癖もあり馴染みにくいものでした。しかし、CoffeeScriptではclassキーワードが用意されており、ごく直感的にクラスを定義できるようになっています。
クラスの基本
まずは、基本的なクラスから定義してみましょう。
配下のプロパティ/メソッドは「名前: ~」の形式で定義します。ただし、コンストラクタの名前はconstructorで固定です。
# Dogクラスを定義
class Dog
# voiceプロパティを定義
voice: 'ワンワン'
# 引数としてnameを受け取るコンストラクタを定義(nameプロパティを設定)
constructor: (@name) ->
# インスタンスメソッドbarkを定義
bark: ->
alert "#{@name}は#{@voice}吠えています。"
return
dog = new Dog 'シロ'
dog.bark()
|
var Dog, dog;
Dog = (function() {
Dog.prototype.voice = 'ワンワン';
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
alert("" + this.name + "は" + this.voice + "吠えています。");
};
return Dog;
})();
dog = new Dog('シロ');
dog.bark(); ……結果:「シロはワンワン吠えています。」
|
「@name」とはプロパティへの参照で、JavaScriptでの「this.name」と同じ意味です。サンプルでは、コンストラクタの引数として直接に「@name」を指定し、中身を空にしていますが、これは以下のコードと同じ意味です。
constructor: (name) ->
@name = name
|
引数で受け取った値をそのままnameプロパティに代入しなさい、というわけです。わずかな省力化とはいえ、コンストラクタでプロパティを初期化するケースが多いことを考えると、こうしたしくみはなかなかに便利です。
静的メンバ
静的メソッド/プロパティを定義するならば、メンバ名の先頭に「@」を付与するだけです。
class Figure
# 静的プロパティpiを定義
@pi: 3.14
# 静的メソッドsquareを定義
@square: (base, height) ->
base * height
alert Figure.pi
alert Figure.square 3, 4
|
var Figure;
Figure = (function() {
function Figure() {}
Figure.pi = 3.14;
Figure.square = function(base, height) {
return base * height;
};
return Figure;
})();
alert(Figure.pi); ……結果:3.14
alert(Figure.square(3, 4)); ……結果:12
|
継承
extendsキーワードでクラスを継承することもできます。子クラスから親クラスのメソッドを呼び出すには、superメソッドを利用します。
class Dog
…中略…
# Dogクラスを継承したSuperDogクラスを定義
class SuperDog extends Dog
bark: ->
# 親メソッドを呼び出し
super()
alert '大声で'
return
dog = new SuperDog 'クロ'
dog.bark()
|
var Dog, SuperDog, dog, _ref,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { …中略… }; ……継承のしくみを関数で実装
Dog = (function() {…中略… })();
SuperDog = (function(_super) {
__extends(SuperDog, _super); ……Dogクラスを継承
function SuperDog() {
_ref = SuperDog.__super__.constructor.apply(this, arguments);
return _ref;
}
SuperDog.prototype.bark = function() {
SuperDog.__super__.bark.call(this); ……親クラスのbarkメソッドを呼び出し
alert('大声で');
};
return SuperDog;
})(Dog);
dog = new SuperDog('クロ');
dog.bark(); ……結果:「クロはワンワン吠えています。」「大声で」
|
メンバーの動的な追加
CoffeeScriptのクラスは、クラスとは言っても、あくまで見かけだけで、その実体はJavaScriptの緩いクラスであるにすぎません。よって、JavaScriptの「緩い」性質はそのまま引き継いでおり、「::」演算子によって、あとから動的にメンバーを追加することもできます。
たとえば以下は、定義済みのDogクラスに対してeatメソッドを追加する例です。
Dog::eat = ->
alert "#{@name}は食べています。"
return
|
Dog.prototype.eat = function() {
alert("" + this.name + "は食べています。");
};
|
「@」キーワードには要注意
CoffeeScriptの「@」は「自分自身」を表すキーワードで、JavaScriptのthisに相当します。JavaScriptでもそうであったように、「@」はその時どきの文脈で指すものが変わることから、しばしば混乱のもとになります。
たとえば、以下のようなケースを見てみましょう。
class Sample # 1 @ はSampleオブジェクト
constructor: (@name) ->
result = document.getElementById 'result'
result.onclick = -> # 2 @ はイベントの発生元
alert "はじめまして、#{@name}さん !"
s = new Sample('山田')
|
本来は、id="result" である要素をクリックしたところで、「はじめまして、●○さん !」というメッセージを表示することを意図しています。しかし、結果は「はじめまして、undefinedさん!」。@nameを正しく認識できていないのです。
これが「@」(this)の落とし穴です。コンストラクタの直下(1)ではオブジェクト自身を指していた「@」が、イベントハンドラーの配下ではイベントの発生元(要素オブジェクト)を指しているのです。そして、要素オブジェクトにはnameプロパティはないので、undefined(未定義)が返されます。
このような挙動を防ぐのが「=>」(二重矢印)の役割です。これによって、関数(メソッド)の外側(この例ではコンストラクタ)で示している「@」をそのまま、関数の内側にも引き継げるようになります。
太字の部分(「->」)を「=>」に書き換えた上で再びコードを実行してみると、今度は「はじめまして、●○さん!」という意図した結果が確認できます。コンパイル済みのJavaScriptも確認しておきましょう。
var Sample, s;
Sample = (function() {
function Sample(name) {
var result,
_this = this; 1 thisはSampleオブジェクト
this.name = name;
result = document.getElementById('result');
result.onclick = function() {
return alert("はじめまして、" + _this.name + "さん !"); 2退避させた_thisでアクセス
};
}
return Sample;
})();
s = new Sample('山田');
|
onclickイベントハンドラーの外で、その時点でのthisを変数_thisに退避させておき(1)、イベントハンドラーの中では_thisでもってnameプロパティにアクセスしているわけです(2)。JavaScriptにおいて、これはthisを固定化する定型的なコードです。
9.3.8 補足:即時関数
本家サイトで提供されている簡易インタプリタを利用していると気付きにくい点ですが、CoffeeScriptで記述されたコードをcoffeeコマンドでコンパイルすると、
(function() {…}).call(this);
|
でラップされたコードが出力されます*20。
- *20 本家サイトの簡易インタプリタでは、「…」の部分だけが出力されます。
一見して奇異にも見える書き方ですが、実はこれ、JavaScriptの世界ではイディオムのひとつで、即時関数と呼ばれます。function命令で関数を定義しておいて、その場で即座に実行することから、このように呼ばれます。
(function() { ……匿名の関数を定義
…スクリプトの本体…
}).call(this); ……関数を実行
|
すぐに実行するのに、なぜわざわざ関数として定義するのか――結論から言ってしまうと、その目的は「すべての変数をローカル変数に封じ込める」ことです。
JavaScriptでは、関数の外で定義された変数はすべてグローバルな変数と見なされます。
しかし、グローバル変数が増えれば、変数名が衝突する危険も高まり、潜在的なバグの原因ともなります。「グローバル変数は最小限にすべき」とは、JavaScriptに限らず、プログラミングの鉄則です。
では、グローバル変数を減らすには、どうしたら良いでしょう。
その解が即時関数なのです。JavaScriptでは関数によってのみスコープが決まりますから、コードをすべて関数で包んでしまえば、すべての変数はローカル変数となります。即時関数とは、関数を(処理のかたまりとしてではなく)スコープの範囲として利用した「便宜的な関数」と言っても良いでしょう。
ライブラリの作成には要注意
以上の性質から、CoffeeScriptでライブラリを作成する際には要注意です。たとえば、hoge.js.coffeeで作成したDogクラスを、foo.js.coffeeから呼び出すようなケースを考えてみます。しかし、これは「'Dog' は定義されていません。」とエラーになってしまいます。
なぜなら、Dogクラスが即時関数でラップされている(=ローカルな名前空間で定義されている)せいで、グローバルには呼び出せなくなっているためです。これを回避するには、Dogクラスの定義に、以下のような記述を追加してください。
class Dog
…中略…
window.Dog = Dog
|
windowオブジェクトは、ブラウザ環境でのグローバルオブジェクトですので、windowオブジェクトのプロパティに値を登録するとは、グローバル変数を登録することと同じ意味です。この例では、ローカル変数(クラス)Dogを、グローバル変数Dogに登録することで、外部に公開しています。
【COLUMN】日付/時刻に関する便利なメソッド
Active Supportでは、標準のDate/Timeオブジェクトを拡張して、より簡単に相対的な日付を取得できます。たとえば、「Time.now.yesterday」で昨日の日付を求めることができます。特に、以下のものはよく利用しますので、是非覚えておいてください。
メソッド | 概要 |
---|---|
yesterday | 昨日 |
tomorrow | 明日 |
prev_xxxxx | 前年/月/週(xxxxxはyear、month、week) |
next_xxxxx | 翌年/月/週(xxxxxはyear、month、week) |
beginning_of_xxxxx | 年/四半期/月/週の最初の日(xxxxxはyear、quarter、month、week) |
end_of_xxxxx | 年/四半期/月/週の最後の日(xxxxxはyear、quarter、month、week) |
また、日付/時間の間隔を求めるならば、「3.months.ago」(3か月前)、「3.months.from_now」(3か月後)のようにNumericオブジェクトのメソッドとして表現できます。太字の部分は、単位に応じて以下のものも利用できます(monthのような単数形も可)。
years、months、days、hours、minutes、seconds
■
次回は、「Sass(SCSS)」の基礎を説明します。
※以下では、本稿の前後を合わせて5回分(第7回~第11回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
8. CoffeeScript入門(前編) ― CoffeeScriptの基本構文
Rubyプログラマーの必須知識「CoffeeScript」の基礎として、基本構文/変数とリテラル表現/演算子/制御構文を解説。書籍転載の8本目(「Part 3《応用編》 第9章 クライアントサイド開発」より)。
9. 【現在、表示中】≫ CoffeeScript入門(後編) ― 関数/オブジェクト指向構文/即時関数
CoffeeScriptの基礎を解説。今回は関数/オブジェクト指向構文/即時関数について説明する(書籍転載の9本目)。CoffeeScriptをマスターしよう。
10. Sass(SCSS)入門
CSSのコードを生成するための言語「Sass(SCSS)」の基礎として、基本的な使い方/スタイル定義のネスト/変数/演算子/関数/ディレクティブ/について解説。書籍転載の10本目(「Part 3《応用編》 第9章 クライアントサイド開発」より)。
11. Apache+Passenger環境/Heroku環境への、Railsアプリの配置
書籍転載の最終回。開発したRailsアプリを本番環境へ配置する方法について説明する(「Part 3《応用編》 第10章 Railsの高度な機能」より)。