Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@johnslith

RailsのCoffeeScriptで共通の処理を別のファイルへ切り出したい

More than 1 year has passed since last update.

タイトルの通りのことをやろうとしたときの、解決方法を見つけるまでに
調べたことを自分用のまとめも兼ねて書いておきます。Rails初学者向けです。
解決策は1番下にあります。

CoffeeScriptとは?

前提知識として。
CoffeeScriptとはすごく簡単に言うと、JavaScriptをより書きやすくするためのものです。
Rubyっぽくかけます。Railsで採用されています。CoffeeScriptはCoffeeScriptのままでは
使えません。CoffeeScriptで書かれたものを普通のJavaScriptに変換して使用します。
そのあたりの変換はRailsが勝手にやってくれます。
なお、RailsはCoffeeScriptもJavaScriptも併用できます
ぐぐるとすぐどんなものかわかると思いますが、書きやすさの片鱗を1ミリだけ見せると以下です。

javascript
alert("Hello");
coffeescript
# 引数をとる関数を呼び出すときの括弧いらない。
# 行末のセミコロンいらない。
alert "Hello"

CoffeeScriptで書くと、他にも色々書きやすくなる点があります。
調べてみてください。

以下本題です。

やりたいこと

やりたいことは単純です。

test1.coffee
sayHello = ->
  alert "Hello"
test2.coffee
sayHello = ->
  alert "Hello"
(なお、上記をJavaScriptで書くとこう)
function sayHello() {
  alert("Hello");
}

test1.coffeeとtest2.coffeeには同じ関数が定義されています。
同じことを2回書いているので、下記のように共通処理をまとめた
common.coffeeを作り、そこから呼び出せるようにしたい。

common.coffee
sayHello = ->
  alert "Hello"
test1.coffee
# common.coffeeを先に読み込んでいる前提
sayHello() # common.coffeeのsayHello関数を呼び出したい(できない)
test2.coffee
# common.coffeeを先に読み込んでいる前提
sayHello() # 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 hogevarを取ると、グローバルな変数とすることもできます。
ただ、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関数を呼び出せる

common.jsのsayHello()はグローバルスコープのエリアで定義されたものです。
よって、test1.jsからもtest2.jsからも呼び出せます。

これがJavaScriptだとできる理由です。

CoffeeScriptだとなぜできないのか(再掲)

CoffeeScriptでできない理由も再度見てみましょう。

CoffeeScriptファイルは、そのファイルで定義したものを即時・無名関数で丸ごと包むことにより、
ローカルスコープに閉じ込めるため、他のファイルからは使用できません。

以下のキーワードを知る必要があります。

  • 即時関数(JavaScriptの機能)
  • 無名関数(JavaScriptの機能)

即時関数とは

即時関数とは、定義した関数が即実行される関数です。よくわかりませんね。

普通の関数はfunctionで定義して、その後、明示的にfunctionの名前で呼び出して実行します。

普通の関数
function sayHello() {
  alert("Hello");
}

sayHello(); // → ここで初めて実行される。

即時関数は下記のように書きます。

即時関数
(function sayHello() { // → ここで定義された時点で即実行される。
  alert("Hello");
}());

何かの初期化処理など1回だけ実行されればいい時など、その関数を再利用する
必要がない時に使います。これが即時関数です。

無名関数とは

一方、無名関数とは名前の無い関数です。よくわかりませんね。

普通の関数は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");

このwindowオブジェクトはグローバルスコープに属しています。
グローバルスコープに属しているものはどこからでも上書き・呼び出しができます。
上書きしてしまうと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って、昔から、こう、、、アレなんですよね。。。

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
johnslith
アイ アム リス

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?