RailsのCoffeeScriptで共通の処理を別のファイルへ切り出したい
RailsのCoffeeScriptで共通の処理を別のファイルへ切り出したい:
タイトルの通りのことをやろうとしたときの、解決方法を見つけるまでに
調べたことを自分用のまとめも兼ねて書いておきます。Rails初学者向けです。
解決策は1番下にあります。
前提知識として。
CoffeeScriptとはすごく簡単に言うと、JavaScriptをより書きやすくするためのものです。
Rubyっぽくかけます。Railsで採用されています。CoffeeScriptはCoffeeScriptのままでは
使えません。CoffeeScriptで書かれたものを普通のJavaScriptに変換して使用します。
そのあたりの変換はRailsが勝手にやってくれます。
なお、RailsはCoffeeScriptもJavaScriptも併用できます
ぐぐるとすぐどんなものかわかると思いますが、書きやすさの片鱗を1ミリだけ見せると以下です。
CoffeeScriptで書くと、他にも色々書きやすくなる点があります。
調べてみてください。
以下本題です。
やりたいことは単純です。
test1.coffeeとtest2.coffeeには同じ関数が定義されています。
同じことを2回書いているので、下記のように共通処理をまとめた
common.coffeeを作り、そこから呼び出せるようにしたい。
しかし、コメントにも書きましたが、実際にはcommon.coffeeからsayHello関数を
test1.coffeeやtest2.cofeeから呼び出すことはできません。
しかしJavaScriptならこれができます。
これはCoffeeScriptの仕様です。
CoffeeScriptファイルは、そのファイルで定義したものを即時・無名関数で丸ごと包むことにより、
ローカルスコープに閉じ込めるため、他のファイルからは使用できません。
何言ってるかわかりませんね。あとで説明します。
その前に、なぜJavaScriptだとできるのかを抑えておきます。
これを理解するには、以下のキーワードを知る必要があります。
スコープとは「範囲」です。イメージしやすいように、ここでは「エリア」と言い換えます。
グローバルスコープとは、「そこに置いたものは、どこからでも呼び出せるようになる」エリアです。
関数の外側で定義された変数や関数が、このエリアに所属することになります。
このエリアで定義された変数や関数は、どこからでも上書きしたり、呼び出すことができます。
ローカルスコープとは、「そこに置いたものは、その場所でしか呼び出せない」エリアです。
関数の内側で定義された変数や関数が、このエリアに所属することになります。
このエリアで定義された変数や関数は、関数の内側以外では、上書きしたり、呼び出すことはできません。
※但し、
ただ、
グローバルとローカルが切り替わってしまうため、基本的にはvarは付けたほうがいい。
なお、CoffeeScriptにはvarはもともと存在せず、グローバル変数にはできません。
ではもう一度、JavaScriptだとなぜできるのか。
再度、JavaScriptのファイルを見てみます。
common.jsのsayHello()はグローバルスコープのエリアで定義されたものです。
よって、test1.jsからもtest2.jsからも呼び出せます。
これがJavaScriptだとできる理由です。
CoffeeScriptでできない理由も再度見てみましょう。
即時関数とは、定義した関数が即実行される関数です。よくわかりませんね。
普通の関数はfunctionで定義して、その後、明示的にfunctionの名前で呼び出して実行します。
即時関数は下記のように書きます。
何かの初期化処理など1回だけ実行されればいい時など、その関数を再利用する
必要がない時に使います。これが即時関数です。
一方、無名関数とは名前の無い関数です。よくわかりませんね。
普通の関数はfunctionのあとに関数名を書きますが、無名関数はその関数名を省略した関数です。
変数に関数を入れる例で書くと以下のようになります。
(JavaScript初学者の方には、え?って思うかもしれませんが、変数は値だけでなく関数も入れられます。)
無名関数は、関数名が必要ないときに使います。
即時関数と無名関数は組み合わせることができます。
ここではこれを即時・無名関数と呼んでおきます。
CoffeeScriptからJavaScriptへの変換処理は、
変換するときに、中身を丸ごと即時・無名関数として包みます。
関数の内側に自分が書いたコードが包まれるので、変換後は全ての変数や関数は
ローカルスコープに閉じ込められるということです。なぜこんなことをするのか。
それは、前述のローカルスコープのところで説明した効果を得たいためです。
定義した関数や変数を、別のファイルの中で同じ名前の関数や変数を使って全く違う定義を
誤ってしてしまうかもしれません。それを防ぐ効果があります。
この効果のために、共通処理を切り出せなくなっています。
いくつか方法があるようです。
JavaScriptにはwindowオブジェクトという特殊なオブジェクトがあります。
先程から良く使っているalert関数も実はwindowオブジェクトが持つ関数の一つです。
つまりこう書けます。このwindowは省略できるため、alertだけで普段使えています。
このwindowオブジェクトはグローバルスコープに属しています。
グローバルスコープに属しているものはどこからでも上書き・呼び出しができます。
上書きしてしまうとalertもなにも使えなくなってしまうので、このオブジェクトに
関数を追加します。
@を使います。@はCoffeeScriptに用意されたJavaScriptのthisの別名です。
JavaScriptのthisは、グローバルスコープのエリアでは、windowオブジェクトを指します。
よって方法1の下記は・・・
下記のように書けます。
以上で終わりです。
新しいバージョンのJavaScriptだとちゃんと関数の外に定義した変数や関数も
グローバルにならないように宣言する方法があるみたいです。
ただ、どこまで今存在するWebブラウザが、そのバージョンに対応しているかは
調べられていません。「ECMAScript 6」とかでぐぐると出てきます。
なにかこう・・・JavaScriptって、昔から、こう、、、アレなんですよね。。。
タイトルの通りのことをやろうとしたときの、解決方法を見つけるまでに
調べたことを自分用のまとめも兼ねて書いておきます。Rails初学者向けです。
解決策は1番下にあります。
CoffeeScriptとは?
前提知識として。CoffeeScriptとはすごく簡単に言うと、JavaScriptをより書きやすくするためのものです。
Rubyっぽくかけます。Railsで採用されています。CoffeeScriptはCoffeeScriptのままでは
使えません。CoffeeScriptで書かれたものを普通のJavaScriptに変換して使用します。
そのあたりの変換はRailsが勝手にやってくれます。
なお、RailsはCoffeeScriptもJavaScriptも併用できます
ぐぐるとすぐどんなものかわかると思いますが、書きやすさの片鱗を1ミリだけ見せると以下です。
javascript
alert("Hello");
coffeescript
# 引数をとる関数を呼び出すときの括弧いらない。 # 行末のセミコロンいらない。 alert "Hello"
調べてみてください。
以下本題です。
やりたいこと
やりたいことは単純です。test1.coffee
sayHello = -> alert "Hello"
test2.coffee
sayHello = -> alert "Hello"
(なお、上記をJavaScriptで書くとこう)
function sayHello() { alert("Hello"); }
同じことを2回書いているので、下記のように共通処理をまとめた
common.coffeeを作り、そこから呼び出せるようにしたい。
common.coffee
sayHello = -> alert "Hello"
test1.coffee
# common.coffeeを先に読み込んでいる前提 sayHello() # common.coffeeのsayHello関数を呼び出したい(できない)
test2.coffee
# common.coffeeを先に読み込んでいる前提 sayHello() # common.coffeeのsayHello関数を呼び出したい(できない)
test1.coffeeやtest2.cofeeから呼び出すことはできません。
しかしJavaScriptならこれができます。
common.js
function sayHello() { alert("Hello"); }
test1.js
// common.jsを先に読み込んでいる前提 sayHello(); // common.coffeeのsayHello関数を呼び出せる
test2.js
// common.jsを先に読み込んでいる前提 sayHello(); // common.coffeeのsayHello関数を呼び出せる
CoffeeScriptだとなぜできないのか
これはCoffeeScriptの仕様です。CoffeeScriptファイルは、そのファイルで定義したものを即時・無名関数で丸ごと包むことにより、
ローカルスコープに閉じ込めるため、他のファイルからは使用できません。
何言ってるかわかりませんね。あとで説明します。
JavaScriptだとなぜできるのか
その前に、なぜJavaScriptだとできるのかを抑えておきます。これを理解するには、以下のキーワードを知る必要があります。
- スコープ(一般的な概念)
- グローバルスコープ(プログラミングの一般的な概念)
- ローカルスコープ(プログラミングの一般的な概念)
スコープとは
スコープとは「範囲」です。イメージしやすいように、ここでは「エリア」と言い換えます。
グローバルスコープとは
グローバルスコープとは、「そこに置いたものは、どこからでも呼び出せるようになる」エリアです。関数の外側で定義された変数や関数が、このエリアに所属することになります。
このエリアで定義された変数や関数は、どこからでも上書きしたり、呼び出すことができます。
test1.js(関数funcの外側がグローバルスコープのエリア)
var hoge = "global" function func() { alert(hoge); // → "global"と表示される。変数hogeはグローバルなのでfunc関数内で使える。 } alert(hoge); // → "global"と表示される。変数hogeはグローバルなのでfunc関数外でも使える。
test2.js
// 事前にtest1.jsが読み込まれている前提 alert(hoge); // → "global"と表示される。変数hogeはグローバルなので、ここでも使える。 // test1.jsの変数hogeは書き換えることができる。 func(); // → "global"と表示される。 var hoge = "replace!"; func(); // → "replace!"と表示される。(test1.jsの変数hogeを書き換えできたことがわかる)
ローカルスコープとは
ローカルスコープとは、「そこに置いたものは、その場所でしか呼び出せない」エリアです。関数の内側で定義された変数や関数が、このエリアに所属することになります。
このエリアで定義された変数や関数は、関数の内側以外では、上書きしたり、呼び出すことはできません。
test1.js(関数funcの内側がローカルスコープのエリア)
function func() { var hoge = "local" alert(hoge); // → "local"と表示される。変数hogeはローカルなのでfunc関数内で使える。 } alert(hoge); // → エラーになる。変数hogeはローカルなのでfunc関数外では使えない。
test2.js
// 事前にtest1.jsが読み込まれている前提 alert(hoge); // → エラーになる。変数hogeはローカルなのでここでも使えない。 // test1.jsの変数hogeは書き換えることができない。 func(); // → "local"と表示される。 var hoge = "replace!"; func(); // → "local"と表示される。(test1.jsの変数hogeを書き換えできなかったことがわかる)
var hoge
のvar
を取ると、グローバルな変数とすることもできます。ただ、
var
の有る無しでローカススコープのエリアで定義しているのに、グローバルとローカルが切り替わってしまうため、基本的にはvarは付けたほうがいい。
なお、CoffeeScriptにはvarはもともと存在せず、グローバル変数にはできません。
JavaScriptだとなぜできるのか(再掲)
ではもう一度、JavaScriptだとなぜできるのか。再度、JavaScriptのファイルを見てみます。
common.js
function sayHello() { alert("Hello"); }
test1.js
// common.jsを先に読み込んでいる前提 sayHello(); // common.coffeeのsayHello関数を呼び出せる
test2.js
// common.jsを先に読み込んでいる前提 sayHello(); // common.coffeeのsayHello関数を呼び出せる
よって、test1.jsからもtest2.jsからも呼び出せます。
これがJavaScriptだとできる理由です。
CoffeeScriptだとなぜできないのか(再掲)
CoffeeScriptでできない理由も再度見てみましょう。CoffeeScriptファイルは、そのファイルで定義したものを即時・無名関数で丸ごと包むことにより、以下のキーワードを知る必要があります。
ローカルスコープに閉じ込めるため、他のファイルからは使用できません。
- 即時関数(JavaScriptの機能)
- 無名関数(JavaScriptの機能)
即時関数とは
即時関数とは、定義した関数が即実行される関数です。よくわかりませんね。普通の関数はfunctionで定義して、その後、明示的にfunctionの名前で呼び出して実行します。
普通の関数
function sayHello() { alert("Hello"); } sayHello(); // → ここで初めて実行される。
即時関数
(function sayHello() { // → ここで定義された時点で即実行される。 alert("Hello"); }());
必要がない時に使います。これが即時関数です。
無名関数とは
一方、無名関数とは名前の無い関数です。よくわかりませんね。普通の関数はfunctionのあとに関数名を書きますが、無名関数はその関数名を省略した関数です。
変数に関数を入れる例で書くと以下のようになります。
(JavaScript初学者の方には、え?って思うかもしれませんが、変数は値だけでなく関数も入れられます。)
普通の関数
var func = function sayHello() { alert("Hello") }; func()
無名関数
var func = function () { alert("Hello") }; // → sayHelloという関数名を省略できます。 func()
即時関数と無名関数は組み合わせることができます。
ここではこれを即時・無名関数と呼んでおきます。
即時・無名関数
(function () { alert("Hello"); }());
CoffeeScriptからJavaScriptへの変換
CoffeeScriptからJavaScriptへの変換処理は、変換するときに、中身を丸ごと即時・無名関数として包みます。
変換前CoffeeScript
sayHello = -> alert "Hello"
変換後JavaScript
(function () { sayHello = function() { return alert("Hello"); }; }());
ローカルスコープに閉じ込められるということです。なぜこんなことをするのか。
それは、前述のローカルスコープのところで説明した効果を得たいためです。
ローカルスコープとは、「そこに置いたものは、その場所でしか呼び出せない」エリアです。もしたくさんのCoffeeScriptファイルを取り扱うようになった時、あるファイルでせっかく
関数の内側で定義された変数や関数が、このエリアに所属することになります。
このエリアで定義された変数や関数は、関数の内側以外では、上書きしたり、呼び出すことはできません。
定義した関数や変数を、別のファイルの中で同じ名前の関数や変数を使って全く違う定義を
誤ってしてしまうかもしれません。それを防ぐ効果があります。
この効果のために、共通処理を切り出せなくなっています。
解決策
いくつか方法があるようです。
方法1
JavaScriptにはwindowオブジェクトという特殊なオブジェクトがあります。先程から良く使っているalert関数も実はwindowオブジェクトが持つ関数の一つです。
つまりこう書けます。このwindowは省略できるため、alertだけで普段使えています。
window.alert("Hello");
グローバルスコープに属しているものはどこからでも上書き・呼び出しができます。
上書きしてしまうとalertもなにも使えなくなってしまうので、このオブジェクトに
関数を追加します。
common.coffee
window.sayHello = -> alert "Hello"
test1.coffee
sayHello()
test2.coffee
sayHello()
方法2
@を使います。@はCoffeeScriptに用意されたJavaScriptのthisの別名です。JavaScriptのthisは、グローバルスコープのエリアでは、windowオブジェクトを指します。
よって方法1の下記は・・・
common.coffee
window.sayHello = -> alert "Hello"
common.coffee
@sayHello = -> alert "Hello"
test1.coffee
sayHello()
test2.coffee
sayHello()
終わり
以上で終わりです。新しいバージョンのJavaScriptだとちゃんと関数の外に定義した変数や関数も
グローバルにならないように宣言する方法があるみたいです。
ただ、どこまで今存在するWebブラウザが、そのバージョンに対応しているかは
調べられていません。「ECMAScript 6」とかでぐぐると出てきます。
なにかこう・・・JavaScriptって、昔から、こう、、、アレなんですよね。。。
コメント
コメントを投稿