JavaScriptの巧い書き方

/web/javascript

Note: この記事は、3年以上前に書かれています。Webの進化は速い!情報の正確性は自己責任で判断してください。

Webに言語は数あれど、特に玉石混淆の激しいJavascriptの書き方について纏めてみた。間違い指摘大歓迎!

発端はYahoo!の Eric Miraglia による、YUI 式モジュールの作り方をまとめた記事。ざっくりまとめると、以下の手順になる。

  1. YAHOO.myProject.myModule = function () {
  2.  
  3. //"private" variables:
  4. var myPrivateVar = "I can be accessed only from within YAHOO.myProject.myModule.";
  5.  
  6. //"private" method:
  7. var myPrivateMethod = function () {
  8. YAHOO.log("I can be accessed only from within YAHOO.myProject.myModule");
  9. }
  10.  
  11. return {
  12. myPublicProperty: "I'm accessible as YAHOO.myProject.myModule.myPublicProperty."
  13. myPublicMethod: function () {
  14. YAHOO.log("I'm accessible as YAHOO.myProject.myModule.myPublicMethod.");
  15.  
  16. //Within myProject, I can access "private" vars and methods:
  17. YAHOO.log(myPrivateVar);
  18. YAHOO.log(myPrivateMethod());
  19.  
  20. //The native scope of myPublicMethod is myProject; we can
  21. //access public members using "this":
  22. YAHOO.log(this.myPublicProperty);
  23. }
  24. };
  25.  
  26. }(); // the parens here cause the anonymous function to execute and return

新しく名前空間を作り、そこに関数を割り当て、その中で変数やメソッドを作成した後、戻り値を設定する。

記事ではYUI を使用した場合で解説しているけど、一般的かつ包括的な方法を用いているので、他のフレームワークや素の書き方にも応用できる凄く便利な書き方になっています。

つ~ことで、この書き方を補足する形で)匿名関数やクロージャについて調べてみました。

名前空間

Javascriptの名前空間については、24 ways の記事を足がかりとすると解りやすい。記事によると、

  1. function foo() {
  2. ... goodness ...
  3. }

上記の関数定義と...

  1. var foo = function() {
  2. ... goodness ...
  3. }

上記の、変数への関数割り当ては同じ働きをします。つまり、グローバル変数に関数を割り当てているって意味で同義なんですね。

普通の関数定義が実はグローバル変数設定と同じなんだと理解すれば、Javascriptコードを名前空間の階層構造としてて捉えることができるので、Prototype.js みたいなフレームワークを眺めたときにも理解しやすいんじゃないかと思います。

ちなみにグローバルな名前空間を汚す... つまりグローバル変数をむやみに増やすことは、速度や保守の面でなるべく避けるべきと言われてますね。

参照: 効率的な JavaScript, グローバル変数を使うのはやめよう

匿名関数

匿名関数を理解するには、IBMの記事が解りやすい。ザクッと紹介すると...

  1. var sum = function(x,y,z) {
  2. return (x+y+z);
  3. }(1,2,3);
  4. alert(sum);

...のように書けます。最後の「(1,2,3);」の部分が引数になりますので、(処理と値の分離が出来て)デザイナとの分業が楽です。

匿名関数はいろんな状況で使用されるけど、特に覚えておきたいこととして、アニメーション使用時とかに使うsetInterval() や setTimeout()での使い方があります。

  1. setInterval('updateResults()',1000);
  2. setInterval(updateResults,1000);

上記の例では、文字列として指定した1.の例より、関数として参照した2.の例のほうが速い。なので例えば以下のように、匿名関数でコードを包む癖を付けておくと便利です。

  1. setTimeout(function(){ alert('First!'); alert('Second!');}, 5000);

参照: 効率的な JavaScript, setTimeout() や setInterval() には文字列でなく関数を渡そう

また、匿名関数を使ってスコープを一段下げてやることで、関数内のローカル変数をグローバル変数のように扱うこともできる。この辺はクロージャでよく使います。

  1. function(){ // 匿名関数でスコープを一段下げる
  2. var msg = "sage"; // msgはグローバル変数ではない
  3. window.onunload = function(){ // グローバルオブジェクトwindow.onloadに関数を付加
  4. alert( msg ); // msgはクロージャによる参照
  5. } // close function
  6. }();

参照: Collection & Copy, 関数、オブジェクト、クロージャ

あとは関数を書いた後、以下のように、括弧の中に入れてから引数を渡して評価することもできる。定義直後のチェック用や使い捨て用とかに使えます。

  1. ( function(x,y,z){... goodness ...} ) (1, 2, 3);

クロージャ

いろいろ調べてはみたけれど、どうにもクロージャについては何となくイメージが伝わってくるだけで具体的な言葉にするのは難しいので、定義や性質だけ羅列しておくことにします。

  • 関数本体の定義とそれを評価するための環境を合わせてクロージャと呼ぶ
  • クロージャの概念を用いれば,オブジェクト指向プログラミングの基本要素であるオブジェクトを作成することができる
  • グローバルな名前空間を占有しない
  • 遅延評価される(呼び出されるまで何も実行しない)
  • 個々のイベントハンドラごとに独立した状態を簡単に持たせされる
  • クロージャはオブジェクトの特殊なサブタイプとして扱われる
  • 参照している変数は、クロージャをつくった関数の終了後も存続する
  • 静的スコープを実現する手段
  • 関数定義はクロージャの変数への束縛にすぎない
  • オブジェクトのインスタンスではなく関数の呼び出しに束縛されている
  • オブジェクトのメソッドは、単にプロパティにクロージャが代入されているもの

どうも関数内で定義したローカル変数を関数内関数で扱う仕組みのことを言うようだ。で、関数内関数は呼び出し元の関数とはスコープが違うから、元の変数を使っても独立した値を保持できる、と。

  1. function newCounter() {
  2. var i = 0;
  3. return function() { // anonymous function
  4. i = i + 1;
  5. return i;
  6. }
  7. }
  8.  
  9. c1 = newCounter();
  10. alert(c1()); // 1
  11. alert(c1()); // 2
  12. alert(c1()); // 3

ある関数型がクロージャか、関数定義か、メソッドかは、呼び出し元が関数なのか変数なのかインスタンスなのかで決まるっぽい?

となると、一番最初のYUIの例は、クロージャを使ったカプセル化を利用しているってことになるのかな?

参照

どっかJavascriptの書き方でためになりそうなサイト知ってましたら、教えてください m(__)m

Note: スパム対策が面倒なので、コメント投稿を廃止しました。以前のコメントは残します。
ご意見・ご要望はtwitter@sigwygかはてブコメントにて。

12 Comments

kkishi wrote:
>ある関数型がクロージャか、関数定義か、メソッドかは、呼び出し元が関数なのか変数なのかインスタンスなのかで決まるっぽい?
javascriptの関数に型はありません。全て「関数オブジェクト」です。オブジェクトのプロパティの参照が関数オブジェクトであるときメソッドと呼ぶに過ぎません。
>となると、一番最初のYUIの例は、クロージャを使ったカプセル化を利用しているってことになるのかな?
そうですね。javascriptにはそもそも名前空間という概念がありませんのでクロージャを利用する事になります。
なんだか、名前空間とスコープの区別が付いていないような印象を受けます。 2007–06–17 07:12
Sig. wrote:
コメントありがとうございます。

 ・ Javascriptの「関数」に型はない
 ・ Javascriptに名前空間と言う概念はない

ああ! つまり関数はどう使おうが「関数」であって、クロージャやメソッドは技名みたいなもんなんですね。だいぶすっきりしました。

> なんだか、名前空間とスコープの区別が付いていないような印象を受けます。

そう言われると、自信はないかもしれない^^;
スコープについても追記しておくべきですね。
ただ、

var oriLibrary = {
version:'1.0.0'
}

...と書いた上で

oriLibrary.oriFunction = function() {
... goodness ...
}

...として oriLibrary.oriFunction() とでもすれば、これは立派な名前空間じゃん! とか思ったりはします。
JavaScriptはXMLのように言語として名前空間をサポートしている訳ではないけれど、「名前空間」という概念を持ち込むことは可能だ、とか。

ただ、オブジェクト間でスコープが完全に独立しているかどうかはイマイチ確信が無いんですよね。これは仕様の話ではなく、実際の動作の話なんですけど。そこら辺、どうなっているんでしょうね? 2007–06–17 16:43
kkishi wrote:
>「名前空間」という概念を持ち込むことは可能だ
そうですね。擬似的に名前空間を作り出しているわけです。

クロージャは関数を静的スコープで実行しようとするときに必要となる副産物のようなものです。
メソッドは技名だけどクロージャは違うかな。
javascriptではあらゆる関数はクロージャです。
下の例のように、関数実行後も内部で定義したデータにアクセス出来る時に特にクロージャと呼んでいるようですね。

var withdraw = function () {
var balance = 100;
return function (amount) {
if (balance >= amount) {
balance -= amount;
return balance;
} else {
return false;
}
}
}()

console.log(withdraw(50));
console.log(withdraw(25));
console.log(withdraw(30));

このコードを実行すると

50
25
false

と表示されますね。二つ無名関数が書かれていますが、内側の無名関数内でbalanceへの参照があるので
外側の無名関数内で定義された変数balanceが破棄されることなく永続的にアクセス出来るようになっています。


>ただ、オブジェクト間でスコープが完全に独立しているかどうかはイマイチ確信が無いんですよね。
スコープが完全に独立していないとはどのような場合を指していますか。 2007–06–17 22:30
Sig. wrote:
おぉっ、そういう形でも引数を渡せるんですね。

> javascriptではあらゆる関数はクロージャです。
なるほど。繋がってきました。
参照している変数がクロージャをつくった関数の終了後も存続する仕組みな訳ですね。
静的スコープって概念からも理解が進みそうな感じです。


> スコープが完全に独立していないとはどのような場合を指していますか。
いや、これはまだ漠然とした不安みたいなもので、はっきりした言葉で言いにくいんですよ。
強いて言うなら、オブジェクトAに格納した値を別のオブジェクトBから参照して変更を加えたとき、元のオブジェクトAの値はどうなっているのかとか、そんな感じで。
これまでの話で言えば、初回の参照以降は「同じ名前だけど別のもの」として扱われるような気がするんですけど、それが横や斜めじゃなくて直接上位のオブジェクトから引っ張ってたら何か影響があるのか、とかとかなんか色々浮かんできて考えが纏まらないんですよ。

とりあえずスコープをもっかい洗ってみます。 2007–06–18 02:16
kkishi wrote:
順番からするとまず静的スコープありきでその実現手段としてクロージャがあるわけですからね。むしろクロージャなんてオマケですよ(もちろん言語仕様的には、ですが。実際プログラミングする上では不可欠です)。

>オブジェクトAに格納した値を別のオブジェクトBから参照して変更を加えたとき、元のオブジェクトAの値はどうなっているのかとか
格納が何を指しているのかが分かりませんが、オブジェクトAのプロパティを指しているなら問題なく変更する事が出来るし、クロージャAの中で内部定義されているローカル変数を指しているならその変数への参照を保持した関数でも定義しておかない限り変更できません。そもそも参照する手段が無いですから。
例えば上の例に挙げたコードの中のローカル変数balanceはwithdrawに束縛された(内部でreturnされている)関数経由でないと絶対に変更する事が出来ません。これが成り立たない処理系ってのはまず、ありえないでしょう。

クロージャを理解する上でお勧めなのはSICPの3章ですね。関数評価のモデルを考えていたはずがいつの間にかオブジェクトを実装する能力が手に入ったりして面白いですよ。上のコードもschemeコードの翻訳だし。 2007–06–18 07:43
kkishi wrote:
ここなんかも参考になると思います。良かったら見てみてください。
[ひ日誌]
http://www.fobj.com/hisa/d/20050924.html 2007–06–18 08:06
Sig. wrote:
これは良いものを...!
ありがとうございます。
次のエントリのテーマが決まりましたよ~ ^^b 2007–06–18 17:04
トオリスガリ wrote:
># (function(){ // 匿名関数でスコープを一段下げる
(が一つ多い様ですよ。
hashとcloserで構成された奇妙な言語であるjavascriptはlexical scopeなのでその関数がどこで定義されたのかがその関数がアクセスできる変数を規定することになります。
なのでオブジェクトのメソッドも別の場所で定義された関数がオブジェクトに拘束されている場合(通常あまりないはず)はオブジェクトの変数にアクセスできなかったりします。 2007–06–20 00:57
Sig. wrote:
> (が一つ多い様ですよ。
おぉ、ありがとうございます。
修正ついでにちょこっと追記しておきました。

JavaScriptが「hashとcloserで構成された言語である」というのは良い表現ですね。凄く解りやすい。素直に納得できました。 2007–06–20 12:16
トオリスガリ wrote:
いえ、自分でも気に入って増田で解説記事を書いてみました。 2007–06–25 08:59
Sig. wrote:
ひょっとしてコレかな?
http://anond.hatelabo.jp/20070620200618 2007–06–26 11:26
トオリスガリ wrote:
それですね。
http://anond.hatelabo.jp/20070622101313
こちらを見てくれた方がうれしかったりするんですけど。 2007–06–26 22:48