LoginSignup
5
4

More than 5 years have passed since last update.

オブジェクト指向初心者の私がJavaScriptにも再入門 「関数・クロージャ・即時関数編」 〜JavaScriptパターン 学習6日目【逃げメモ】〜

Last updated at Posted at 2016-06-03

関数

関数は非常に覚えることがたくさんあるので、複数記事に分かれています。今回は「コールバック」に続き「クロージャ・即時関数」に触れていこうと思います。

クロージャ

関数はオブジェクトなので戻り値に使う事ができます。戻り値に関数を使い、スコープの挙動を利用すれば、外部からアクセスのできない値を扱う事のできるクロージャを生成する事ができます。

クロージャ
//関数式
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は一つ上のスコープで生成されているもので、戻る関数内では宣言されていません。それは スコープチェーンと呼ばれる変数を外へ外へ探しに行く仕組みを使って同一名の変数を探します。

どういう風に流れるかというと、

  1. 戻る関数のスコープ内でcountという変数を探す。→無いので親を見に行く
  2. 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日目【逃げメモ】〜

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4