Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
19
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

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

はじめに

クロージャって何?

クロージャとは、関数内に現れる変数の名前解決が、ローカルスコープだけでなく、関数が定義された場所のスコープも含めて行われる関数のことです。
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
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
19
Help us understand the problem. What are the problem?