関数
関数は非常に覚えることがたくさんあるので、複数記事に分かれています。今回は「コールバック」に続き「クロージャ・即時関数」に触れていこうと思います。
クロージャ
関数はオブジェクトなので戻り値に使う事ができます。戻り値に関数を使い、スコープの挙動を利用すれば、外部からアクセスのできない値を扱う事のできるクロージャを生成する事ができます。
//関数式
var counter = function() {
var count = 0;
//戻り値に関数を指定(ここでは戻る関数と呼ばせて頂きます)
return function() {
//count値を1増やす
return (count += 1);
};
};
//counter関数式の実行した戻り値である「戻る関数」を新しい変数にセット
var aruCounter = counter();
//新しい変数(中身は関数)の呼び出し
console.log(aruCounter()); //1
console.log(aruCounter()); //2
console.log(aruCounter()); //3
//新しいカウンターの作成
var sonoCounter = counter();
console.log(sonoCounter()); //1
console.log(sonoCounter()); //2
console.log(sonoCounter()); //3
//aruCounterとsonoCounterは同一ではない
console.log(aruCounter === sonoCounter); //false
何故値は保持されるのか
aruCounter
に代入される際、counter関数式は1回呼び出されます。counter関数式は 新たにスコープをメモリ上に生成して、count
変数の値は0で生成されます。次の行で出てくるreturn
によって戻る関数がaruCounter
にセットされます。
戻る関数の中身はfunction(){return(count += 1);}
であり、また 新たにスコープが生成されております。count
は一つ上のスコープで生成されているもので、戻る関数内では宣言されていません。それは スコープチェーンと呼ばれる変数を外へ外へ探しに行く仕組みを使って同一名の変数を探します。
どういう風に流れるかというと、
- 戻る関数のスコープ内で
count
という変数を探す。→無いので親を見に行く - counter関数式のスコープ内で'count'という変数を探す。→あったのでこれを使う
という流れになります。もし2番で見当たらない変数だった場合は、更にその親のスコープに探しに行き、最終的にグローバルオブジェクトの変数まで探しに行き、無ければエラーとなります。
今回は、2番で見つかったcounter関数式のcount
を使用するので、このcount
の値を持つスコープもaruCounter
によって一緒に(値の在り処を知るためについでに)参照され続ける事となります。
通常はスコープへの参照が無くなるとガベージコレクタによって消されてしまいますが、参照され続けることで スコープへの参照が無くならず、aruCounter
が消えるまでcount
の値も無くならないといった挙動となります。
何故値は変わらないのか
次に、sonoCounter
が生成される時、同様にcounter関数式が呼び出されます。
関数式は 新たにスコープを作成するので、前回のaruCounter
で使われているcount
とは
別のメモリ領域に生成されたcount
を新たに用意してそちらを参照するようになるので、
同じ関数式を用いても値が競合することはありません。
というような、ガベージコレクタと呼ばれる使われなくなった変数をメモリから解放してくれるという便利なメモリ管理機能とスコープの特性を組み合わせる事で、プライベートな値を保持できる仕組みができるというわけです。
自己定義関数
定義した関数を他の定義に書き換える方法です。
var sayHello = function() {
console.log("Hi");
sayHello = function() {
console.log("Hello");
}
}
sayHello(); //Hi
sayHello(); //Hello
ただ、自己定義関数は新しい変数に代入したものは自己定義は行われません。そして自己定義関数が実行されると新しい定義にプロパティを引き継ぐ事ができないという点は覚えていなければなりません。
var sayHello = function() {
console.log("Hi");
sayHello = function() {
console.log("Hello");
}
}
sayHello.weather = "fine";
console.log(sayHello.weather); //fine
//代入する
var aisatsu = sayHello;
aisatsu(); //Hi
aisatsu(); //Hi
console.log(aisatsu.weather); //fine
//自己定義関数を呼ぶ
sayHello(); //Hi
sayHello(); //Hello
console.log(sayHello.weather); //undefined
console.log(aisatsu.weather); //fine
即時関数
関数を作成した直後に実行される。
- 関数式を使って関数を定義する(関数宣言は行えません)
- 最後に括弧
( )
を追加する事で実行される。 - 関数を変数に代入したくない時は関数全体を括弧で囲む
(function() {
console.log("Hello");
})() //Hello
即時関数のパラメーター
即時関数のパラメーターは宣言は上、実際に渡すのは下で行います。
(function(name, instruments) {
var members = {
"Guitar": "大木",
"Bass": "サトマ",
"Drums": "ピカチュウ"
};
console.log("こんにちは、" + name + "の" + instruments + "の" + members[instruments] + "です")
})("ACIDMAN", "Guitar"); //こんにちは、ACIDMANのGuitarの大木です
一般的には即時関数のパラメーターにはグローバルオブジェクトを渡します。window
を使わずに済み、ブラウザ以外の環境でのコード相互利用性が上がるとの事です。
(function (global){
//処理
console.info(global); //Window {undefined...}
})(this);
即時関数の戻り値
即時関数はカンタンに変数に代入する事ができます。
//即時関数の戻り値をcounterに設定
var counter = (function (cnt){
var count = cnt;
return function() {
return (count += 1 );
}
}(3)); //パラメーターに初期値を入れる
console.log(counter()); //4
console.log(counter()); //5
console.log(counter()); //6
オブジェクトのプロパティを定義する時にも即時関数は役に立ちます。
var greeting = {
message: (function() {
var who = "鈴井貴之";
return "やぁやぁ" + who + "!"; //戻り値は文字列
}()/* 即時実行されて戻り値は文字列に */),
getMsg: function() {
return this.message; //message文字列プロパティを呼んでいる
}
};
console.log(greeting.getMsg()); //やぁやぁ鈴井貴之
console.log(greeting.message); //やぁやぁ鈴井貴之
即時関数はその場で実行されるので、messageプロパティは関数ではなく文字列が設定される事となる。なので、プロパティに1度だけ変数を使ったもので取得したものを使ってあとはプリミティブのように呼び出しだけ使用したい場合などに便利に作用するわけですね。
###グローバルを汚さない処理
このパターンを使うとグローバルを汚染せずにさせたい処理をさせることができます。ブックマークレットやモジュールとして機能を追加したい時に使用されます。
//例えば、ファイル名をmodule1.jsとかにする
(function() {
//処理
}());
感想
「クロージャ」という言葉ですが、近頃は色々な所で見かけましたが、それが何なのか、何のために存在してどう使えば有効か、などほとんど理解していなかったのですが、今回調べていくうちに「なるほど」が重なった感じで良かったです。
ただ、まだカウンターしか作っていないので利点を享受するには色々試してみないとわからない所なので、ちょくちょく使ってみたいと思います。
JavaScriptは本当に書き方が柔軟で、その分読めるようになるまでに覚えることもたくさんあります。関数にもまだ続きがありますので、逃げないように頑張ろうと思います。
リンク
次回はこちら
オブジェクト指向初心者の私がJavaScriptにも再入門 「関数・初期化関連編」 〜JavaScriptパターン 学習7日目【逃げメモ】〜
前回はこちら
オブジェクト指向初心者の私がJavaScriptにも再入門 「関数・コールバック編」 〜JavaScriptパターン 学習5日目【逃げメモ】〜