Panda Noir

JavaScript の限界を究めるブログです。

プログラミング技法講座 その2 無名関数を自在に操ろう

無名関数という関数があります。名前からマイナスなイメージを持つかもしれません。しかし、使いこなせるととても便利です。だから、使い方を見て行きましょう。

無名関数とは

名前の通り無名の関数です。まず関数について見ていきます。関数は次の2通りの書き方ができます。

function sum(a,b){
    return a+b
}
var helloWorld=function(name){
    return "hello, "+name;
}

上は下の省略形です。だから、関数内で新しく関数を定義すると自動でローカル変数になります。グローバルに置きたいときは下の書き方でvarをつけないかそもそも関数外に書くかの2通りです。

今回はあまり関係ありませんが正確には省略形ではないそうです。The difference is that functionOne is defined at run-time, whereas functionTwo is defined at parse-time for a script block.http://stackoverflow.com/questions/336859/var-functionname-function-vs-function-functionname引用訳すと違いはfunctionOne(これは下のhelloWorldのことです)は実行時に定義されますが、functionTwo(これは上のsumのことです)は解析時に定義されます。だそうです。

上の書き方はよく見ると思いますが下の書き方は初めて見たという方もいらっしゃるかとおもいます。実は下の書き方は無名関数を代入しているのです。つまり関数はデータ型の一種なのです。だから数値や文字列のお仲間さんです。※注意:これはjavascriptの話しであり他の言語ではそんなことありません。

さて、では無名関数も出てきたので更に突っ込んだ話をしましょう。

関数は()をつけて呼びますよね。何気なくやっていますがこれあとで重要になってきます。関数をコピーしようとしてvar a=b()みたいにした経験有りますよね。私もその一人です。これはbを実行してその返り値をaに代入するということです。具体的に言いますとvar a=sum(3,4)です。これはみたことありますよね。では関数をコピーするにはどうしたらいいのか。簡単です。var a=sumでいいのです。関数はデータ型の一種ということがわかればこれでできることがわかると思います。ちなみにvar a=document.writeなどはできません。同様にvar a=alertもできません。alertは正確にはwindow.alertだからです。

無名関数の利用法とは

では本題の無名関数を利用する方法です。

まず、クロージャですね。なかなか便利です。クロージャ (クロージャー、Closure) は、プログラミング言語において引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決する関数のことである。引用:wiki意味不明ですね。私もこんなので説明されてもわかりません。今は理解できてますので見て行きましょう。

クロージャとは関数を返す関数で、次のような関数を指します。しかし、関数を返すのははじめの一回のみです。意味不明と思われるかもしれませんね。

var closure=(function(){
    var name="world"
    return function(greet){
        return greet+name;
    }
})();
console.log(closure("hello "));

実行するとhello worldとなります。意味不明な形で見たことある人は少ないと思います。説明していきます。

var closure=(function(){略})()の(function(){})の部分から説明していきます。というかこれぐらいしか説明がない…これはfunction(){}という無名関数を()でくくり、最後に()をつけて実行しています。()でくくる理由は知ってて当然?初級者のためのJavaScriptで使う即時関数(function(){...})()の全てに書いてあります。めちゃくちゃ長いので書きません。興味がある人は見てみてください。

動作を追っていくと 1. (function(){})()を実行する 2. 返ってきた function(greet){略}をclosureに代入する。3. 終わり という感じです。ここで疑問に感じる人がいればすごいと思います。私はこの手のもので1度も疑問に感じたことがありません。

さて、何が疑問かというと2ですね。なぜfunction(greet){略}が返ってきたはずなのにnameが使えるのかというところです。当然といえば当然ですね。nameはfunction(greet){}内で定義されてないのですから。

ではなぜできるのかというとちょっと見方を変えればわかります。

function closure(greet){
    var name="world"
    function hello(greet){
        return greet+name;
    }
    return hello(greet);
}
console.log("hello ");

これを実行すると当然hello worldと出ます。なぜか。それはnameが関数内にあるからです。では先ほどの関数ではnameは無かったことになるのでしょうか?違います。確かに関数が返されますが、nameは呼び出せます。なぜなら自分を包んでいる関数のローカル変数にアクセスできるからです。なんとなーく理解出来ればいいと思います。

ではこれのどこが嬉しいのか。例えばループで回して関数を定義する時です。

var array=["a","b","c"];
for(var i=0;i<array.length;i++){
    document.getElementById(array[i]).onclick=function(){
        console.log(array[i]);
    }
}

面倒になってonclickを使いましたがあまりよろしくありません。良い子は真似してはいけません。さて、これを実行するとaという要素をクリックしたらaと表示されます。嘘です。されません

なぜされないのかというと、console.log(array[i])のiはループが終わったあとのiであり、ループしてる時のiが代入されるわけではないからです。

ではaをクリックしたらa、bをクリックしたらbと表示させるにはどうしたらいいのか。クロージャを使えばできます。

var array=["a","b","c"];
for(var i=0;i<array.length;i++){
    document.getElementById(array[i]).onclick=(function(i){
        return function(){
            console.log(array[i]);
        }
    })(i)
}

これでiを代入してキープすればできます。

終わりに

あまり関係ないですが文章がやはり見づらいですね。もっと何とかして行きたいです。