LoginSignup
22
19

More than 5 years have passed since last update.

JavaScriptのクロージャの初歩の初歩

Posted at

はじめに

クロージャって何?

クロージャとは、関数内に現れる変数の名前解決が、ローカルスコープだけでなく、関数が定義された場所のスコープも含めて行われる関数のことです。
Perfect PHP

関数の中に関数を書く

クロージャを理解するために、関数の中の関数を使うコードを書いてみます。まずは関数リテラルをそのままreturnする方法。

inner_func1.js
function f() {
    return function() {
        console.log("Hello!");
        return;
    };
}
var fn = f();
fn(); //Hello!
fn(); //Hello!

あるいは、こんな風にも書けます。関数リテラル式を変数に入れてからreturnする方法。

inner_func2.js
function f2() {
    var inner = function () {
        console.log("Yo!");
        return;
    };
    return inner;
}

var fn2 = f2();
fn2(); //Yo!
fn2(); //Yo!

こんな風に書き直すこともできます。関数を一旦定義してから、関数名をreturnする方法。

inner_func3.js
function f3() {
    function inner() {
        console.log("Hi!");
        return;
    };
    return inner;
}

var fn3 = f3();
fn3(); //Hi!
fn3(); //Hi!

クロージャの例

Perfect JavaScriptのクロージャのサンプルコードです。

sample.js
function f() {
    var cnt = 0;
    var inner = function() {
        return ++cnt;
    };
    return inner;
}

var fn = f();
console.log(fn()); //1
console.log(fn()); //2
console.log(fn()); //3

cntは関数fの外からは参照できないですが、クロージャ(inner)からは参照することができます。ここまで来ると、冒頭の「クロージャって何?」の意味が分かる気がします。
なお、ここではinner_func2.jsの形式を使って関数を定義しましたが、他の形式でも同様の動作をさせることができます。

二つの関数を入れる

関数fの中に、2つのクロージャを入れることもできます。

double_closure.js
function f() {
    var cnt = 0;
    var add = function() {
        return ++cnt;
    };
    var sub = function() {
        return --cnt;
    };
    return {add:add, sub:sub};
}

var fn = f();
console.log(fn.add());
console.log(fn.add());
console.log(fn.sub());
console.log(fn.sub());

2つのクロージャから、同じ変数cntを参照できます。

Classっぽく書く

thisを使って、クラスっぽく書くこともできます。

my_class.js
function MyClass() {
    this.cnt = 0;
    this.add = function() {
        return ++this.cnt;
    };
    this.sub = function() {
        return --this.cnt;
    };
    this.show = function() {
        console.log(this.cnt);
    };
    return this;
}

var fn = MyClass();
console.log(fn.add()); //1
console.log(fn.add()); //2
console.log(fn.sub()); //1
console.log(fn.sub()); //0

// 直接操作できちゃう
fn.cnt = 10;
fn.show(); //10
fn.add();
fn.show(); //11

MyClass内の関数から参照する変数を、this.cntとすれば外部から直接参照可能になります。次のようにローカル変数(var cntで宣言)とすれば外部からは直接参照不可になります。

my_class2.js
function MyClass() {
    var cnt = 0;
    this.add = function() {
        return ++cnt;
    };
    this.sub = function() {
        return --cnt;
    };
    this.show = function() {
        console.log(cnt);
    };
    return this;
}

var fn = MyClass();
console.log(fn.add()); //1
console.log(fn.add()); //2
console.log(fn.sub()); //1
console.log(fn.sub()); //0

// 直接操作できない
//fn.cnt = 10;
fn.show(); //0
fn.add();
fn.show(); //1

初期値を設定する

fの引数を利用するなどして、クロージャから参照する変数の初期値を与えることもできます。

set_init.js
function f(cnt_init) {
    var cnt = cnt_init;
    var add = function() {
        return ++cnt;
    };
    var sub = function() {
        return --cnt;
    };
    return {add:add, sub:sub};
}

var fn = f(10);
console.log(fn.add()); //11
console.log(fn.add()); //12
console.log(fn.sub()); //11
console.log(fn.sub()); //10
22
19
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
22
19