スクリプトへの引数渡し

たとえば以下のような場合。

  • CoffeeScript、Javascriptの外部スクリプトに引数を渡したい
  • 外部スクリプトの定数定義を柔軟に変更可能なようにしたい

定数となる変数定義をスクリプト内に書くと、定数の値を変更したいときに再コンパイルやアップロードなどが必要になって煩雑。

以下の方法が考えられる。

  • HTMLの外部スクリプト呼び出しより前に、変数定義のscript要素を書く(変数のスコープはグローバルになる)
    • その際、行の末尾にセミコロン(;)をつけること
  • 外部スクリプト内で上で定義した変数を利用する

HTML

外部スクリプト

CoffeeScriptも実行時にはJSにコンパイルしたものを読み込むので、変数定義もJavascriptの文法で末尾にセミコロン(;)をつけなければならない。

上の例では、定数を意識して変数名を大文字のスネークケースとしたが、通常の変数名でも実行に問題はない。

 

CoffeeScript – geom2dパッケージ

パッケージ名

taustation_geom2d

クラス

taustation_geom2d.Vector
2次元ベクトルを保持し、各種演算を行うメソッドを提供する。
taustation_geom2d.MovingAgent
位置と速度を保持し、互いの位置関係を判定するメソッドを提供する。

コードの骨組み

 

CoffeeScript – geom2d.Vectorクラス

パッケージ

taustation_geom2d

クラス名

taustation_geom2d.Vector

クラス仕様

概要

x、y二つの成分を持ち、絶対値の計算や加減乗算など2次元ベクトルの代数演算を行うメソッドを提供する。

クラス変数

なし

クラス・メソッド

distance(pos1, pos2)
2つのVectorオブジェクトpos1、pos2を点を表す位置ベクトルとして、2点間の距離を返す。

インスタンス・プロパティ

x
ベクトルのx成分
y
ベクトルのy成分

コンストラクタ

constructor(x, y)
x成分とY成分を指定してVectorのインスタンスを生成する。

インスタンス・メソッド

基本メソッド

set(x, y)
Vectorのx成分とy成分をセットする。
toString()
Vectorの文字列形式の表現を返す。
getClone()
元のVectorオブジェクトと同じ内容のクローンを生成して返す。

演算

abs
Vectorの絶対値(長さ)を返す。
times(coeff)
Vectorに定数coeffを乗じたベクトルを新たに生成して返す。
timesEq(coeff)
Vectorに定数coeffを乗じて変更する。新しいオブジェクトは生成しない。
div(coeff)
Vectorを定数coeffで除したベクトルを新たに生成して返す。
divEq(coeff)
Vectorを定数coeffで除して変更する。新しいオブジェクトは生成しない。
plus(other)
Vectorにotherベクトルを加えたベクトルを新たに生成して返す。
plusEq(other)
Vectorにotherベクトルを加えて変更する。新しいオブジェクトは生成しない。
minus(other)
Vectorからotherベクトルを減じたベクトルを新たに生成して返す。
minusEq(other)
Vectorからotherベクトルを減じて変更する。新しいオブジェクトは生成しない。
inprod(other)
Vectorとotherベクトルの内積値を返す。
ang(other)
Vectorとotherのなす角を計算する(0~π)。

新たなVectorの生成

getUnit()
Vectorと同じ向きの単位ベクトル(長さが1)を新たに生成して返す。
getInverse()
Vectorと逆向きのベクトルを新たに生成して返す。
getOrthogonalR()
Vectorから右に90度回転したベクトルを新たに生成して返す。
getOrthogonalL()
Vectorから左に90度回転したベクトルを新たに生成して返す。
getUnitOrthogonalR()
Vectorから右に90度回転した単位ベクトルを新たに生成して返す。
getUnitOrthogonalL()
Vectorから左に90度回転した単位ベクトルを新たに生成して返す。

判定

isHeadingToRightOf(other)
このVectorオブジェクトの進行方向が、引数のVectorオブジェクトotherの進行方向より右方を指しているときtrue、左方を指しているときfalseを返す。
isHeadingToLeftOf(other)
isHeadingToRightOf()と左右逆の判定結果を返す
isInFrontOf(movingAgent)
このVectorオブジェクトが位置ベクトルとして、その点が引数のMovingAgentオブジェクトの前方(左右180度より進行方向の側)にあるときtrue、後方にあるときfalseを返す。真横の時は前方と判定される。
isIntheRearOf(movingAgent)
isInFrontOf()と前後逆の判定結果を返す。
isOnTheRightOf(movingAgent)
このVectorオブジェクトが位置ベクトルとして、その点が引数のMovingAgentオブジェクトの右方にあるときtrue、左方にあるときfalseを返す。進行方向の延長線上にあるときは右方と判定される。
isOnTheLeftOf(movingAgent)
isOnTheRightOf()と左右逆の判定結果を返す。

コード

 

CoffeeScript – グローバル変数

グローバルにならない

CoffeeScriptでグローバル変数を期待して以下のように書いたとする。

一見すると問題なく動いているように見えるが、これを別ファイルのパッケージにして動かそうとすると問題が顕在化する。以下のように”globalVarが存在しないよ”というエラーが出るのである。

これら2つのファイルをjsにコンパイルしてブラウザのscript要素で読み込んだ場合、そのイメージは以下のようになる。

パッケージでグローバル定義したつもりだが、コンパイル時に無名関数でクロージングされるため、グローバルにならず、他のファイルから読み込むことができない。

解決策1

グローバル変数をwindowオブジェクトの要素にしてしまう。

解決策2

rootコンテキストで、以下のパターンを使う。

 

CoffeeScript – パッケージ管理

パッケージ化の方法について

簡単な方法~齟齬あり

クラス/オブジェクトのプロパティにする

クラスやオブジェクトをパッケージの名前とし、パッケージ内のクラスなどはそのプロパティとして定義する。

ただしこの方法は、最適とは言えない。

1行目で名前空間に用いるクラスを定義し、7行目でMyClassPack1のプロパティとしている。

10行目で名前空間に用いるオブジェクトを定義し、16行目でMyClassPack2のプロパティとしている。

20行目でMyClass単独を用いた場合、直前に13行で定義されたMyClassが参照される。

パッケージ内での名前参照の祖語

パッケージ間で名前が重複する場合、下手をすると想定外のオブジェクトが使われてしまう。

次の例は、Pack1Pack2の2つのパッケージで同じクラス名を使い、各パッケージの中ではパッケージ名なしで使おうとしたもの。

ところが29行目で、Pack1のクラスのはずのところがInnerClassにはPack2のものが使われてしまっている。

これは、25行目以降の実行文の前にグローバル変数InnerClassの内容Pack1のものからPack2のものへと上書き定義されたため。

結局この方法だと、各パッケージの中でもクラス名の前にパッケージ名をつけないといけないようで、コーディング上は元のクラス名に識別子を組み込むのとあまり変わらない。

有効な方法~即時実行関数

先の方法は、すべてグローバル領域で行われていたため、パッケージ内でのみ参照するクラスも上書きされてしまった。

そこで、パッケージごとに即時実行される無名関数で括ってみる。

1行目のようにグローバル領域でパッケージ名に使うオブジェクト/クラスを定義し、パッケージごとに即時無名関数で括る。

パッケージ内ではパッケージなしでアクセスし、外からアクセスするときはパッケージ名を付してアクセス。

これで一応、パッケージ化とコーディングの簡易化を両立させることができる。

パッケージ名について

パッケージ名は、「世界規模での名前衝突を避ける」ように潔癖を極めるなら、Javaで行われているようなドメイン名管理が答となる。

なお、Javaの場合はimport文で指定したパッケージはクラス名のみで使えるが、Javascriptではエイリアスを明示的に指定しなければならないらしい。

JavaScriptスタイルガイドでは、「長い型名をエイリアスし可読性を向上させる」ために以下のように示唆されている。

“ローカルのエイリアスを使うことで長い型名の可読性を向上できる場合はそうしてください. エイリアスの名前は型名の最後の部分にしてください.”

なお、npmのサイトでいろいろなパッケージを見てみると、区切りにはアンダースコア(_)ではなくマイナス(-)を使っている。

 

CoffeeScript – タイマ

setInterval

setInterval()関数の引数に、アニメーションフレームを描画する関数と時間間隔を指定する。時間間隔はミリ秒単位。

描画の開始/停止をコントロールするには、setInterval()関数の戻り値を変数に保存しておき、停止するときにclearInterval()関数の引数でその変数を指定する。

一時停止/再開の機能は特に実装されていないが、描画関数を工夫することで可能。

描画関数を、以下のように無名関数で書くこともできる。

この処理を他のオブジェクトのメソッドの中に書いて、オブジェクトのプロパティを利用する場合は、1行目のアローをファットアロー(=>)にする。

 

CoffeeScript – クラスのメソッド

アクセス可能性

以下のコードで、クラスの関数へのアクセス可能性を確認する。

各関数へのアクセス可能性は以下の通り。

定義\引用 クラス インスタンス メソッド内
(@なし)
 メソッド内
(@つき)
fnc: ->  TypeError  ○ ReffError
@fnc: ->  ○ TypeError ReffError TypeError
fnc = -> TypeError TypeError TypeError
@fnc = -> TypeError ReffError TypeError

なお、上記コードの定義部分のJavaScriptコンパイル結果は以下の通り。

クラス・メソッド

クラス内で@つきでメソッドを定義すると、クラスメソッドになる。

上記のいずれもJavaScriptへのコンパイル結果は同じになる。

クラスメソッドは、クラス内で呼び出すときでもクラス名をつける。

publicなインスタンス・メソッド

最も多く用いる形で、クラス内でコロン(:)によって関数を定義すると、インスタンス・メソッドになる。

JavaScriptへのコンパイル結果では、prototype宣言されている。

同じクラスのメソッド内で呼び出すときは、@つきで。

privateなクラス・メソッド

クラス内でfnc = ->のように定義すると、クラスの外からは見えないprivateなメソッドになる。

JavaScriptコードでは、クラス内のスコープで宣言されており、外からは見えない。

 

CoffeeScript – クラスのプロパティ

アクセス可能性

以下のコードで、クラスに関する変数へのアクセス可能性を確認する。

各変数へのアクセス可能性は以下の通り。

定義\引用 クラス インスタンス メソッド内
(@なし)
メソッド内
(@つき)
クラス直下 v=val undef undef undef
@v=val undef undef RefError undef
v:val undef RefError
@v:val undef RefError undef
コンストラクタ v=val undef undef RefError undef
@v=val undef RefError

この表から、以下のように整理できる。特にpublicなインスタンス・プロパティとstaticなクラス・プロパティを使っていくことになる。

クラス直下で@v:val定義 staticなクラス・プロパティ
クラス直下でv:val publicなインスタンス・プロパティ
 コンストラクタで@v=val publicなインスタンス・プロパティ
 クラス直下でv=val  privateなクラス・プロパティ

なお、定義部分のJavaScriptへのコンパイル結果は以下の通り。

クラス・プロパティ

クラス直下で@v:valでプロパティを定義する場合、プロパティはクラスに一つの共有のものとして扱われる。コンパイルされたJavaScriptコードでは正に”[クラス名].[プロパティ名]“となっている

インスタンスの中から参照する場合も、[クラス名].[プロパティ名]で参照する。

publicなインスタンス・プロパティ

クラス直下でv:valと定義した場合とコンストラクタで@v=valと定義した場合は、いずれもインスタンス・プロパティとして扱われる。

挙動としては同じなのでどちらでもよさそうだが、インスタンス・プロパティとしてはコンストラクタで定義する形式の方がよいと考えた。

以下のように、それぞれの定義方法でJavaScriptのコードが異なるが、メソッドのようにクラス内で共通化したい場合はprototypeがよいが、そもそもインスタンスごとに異なる値をとることを想定しているプロパティの場合は、thisによる定義の方が意味に即している。

privateなクラス・プロパティ

クラス直下でv=valと定義すると、そのクラスの各インスタンスで共有されたprivateなプロパティになる。

JavaScriptコードを見るとクラス宣言の最初でvar宣言されているため、クラス内スコープで外からは見えない変数となる。

 

CoffeeScript – jQueryによるINPUT要素の操作

jQueryによるINPUT要素の操作

ボタンのテスト

ここではボタン操作に対するイベントを確認している。

ボタンはinput要素のタイプの一つとして作成される。
ボタンが押された時のイベントはonclick属性で定義してもよいが、ここではそれを使わず、jQueryのハンドラで処理している。

ボタンが押下されたときのイベントは、(そのボタンが動的に後から追加されたものでなければ)click()で捕捉される。

テキストボックスのテスト

入力:
出力:

ここではテキストボックスの内容の取得や書き込みについて確認している。

テキストボックスの内容の取得や設定はval()メソッドで行う。

チェックボックスのテスト

チェックボックスの確認。

チェックボックスのチェック状態の取得には、prop()メソッドを用いる。

ラジオボタンのテスト

通知エリア

  

ラジオボタンの動作を確認している。ここでは選択状態が変わるごとにイベントが発生して、通知エリアの表示が変わる。

上段はブラウザ側のonchange属性でイベントハンドラを指定し、下段はjQueryのハンドラを使っている。

セレクトボックスのテスト

通知エリア




セレクトボックス(ドロップダウンリスト)の例。

  • ボックス内の選択肢を選ぶたびに通知エリアの表示が変わる
  • “LONDONへ”ボタンを押すと、ロンドンが選択された状態になる。
  • “追加”ボタンで選択肢に”パリ”が追加され、”削除”ボタンで削除される

セレクトボックスの状態変化のイベントは、change()メソッドで捕捉される。

“追加”と”削除”のボタンはprop()メソッドを使って選択状態をトグルさせているが、ページのテーマ/デザインによっては明確にはわからないことがある。

 

CoffeeScript – CSソースとJSコンパイル結果

概要

様々なソースに対するコンパイル結果。

クロージャと変数のスコープ

グローバルなし

グローバルのつもりでも、必ず無名関数で括られる。

変数の宣言位置

変数は、それが属する関数の先頭で必ずvar宣言される。つまり、各変数はそれが属する関数内のローカルスコープを持つ。

引数なしの無名関数によるクロージャ

引数ありの無名関数によるクロージャ

クロージャとdoの違い

クラス

プロパティの宣言