質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.84%

  • JavaScript

    14789questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

JavaScript、クロージャーにつきまして

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 756

Behemoth

score 19

お世話になっております。
JavaScriptを勉強中のものです。

関数に関しての質問です。
「開眼!JavaScript」(オライリージャパン)101pにクロージャーの例として、下記のコードが挙げられていました。

var countUpFromZero = function(){
         var count = 0;
     return function(){
         return ++count;
     }
 }();

 console.log(countUpFromZero()) //1
 console.log(countUpFromZero()) //2
 console.log(countUpFromZero()) //3


そこで、これを

var countUpFromZero = function(){
     return function(){
         return ++count;
     }
}();
var count = 0;


と書き換えてみたところ、同じ挙動をしました。
スコープチェーンは関数が定義された時点で生成されるとのことですが、このばあい、countUpFromZeroによって返される無名関数がその後に定義される変数count を参照できるのはどうしてでしょうか?

どなたかよろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • HayatoKamono

    2018/06/23 10:56 編集

    回答ではないですが、参考として、次のキーワードのいずれかで検索してみて下さい。変数 巻き上げ、ホイスティング、hoisting

    キャンセル

  • Behemoth

    2018/06/23 11:47

    ごヒント、ありがとうございます。まずはそこから理解を深めていきたいです。

    キャンセル

回答 4

+2

HayatoKamonoさんと同じく、ぱっと見は巻き上げと思いました。

var count = 0;前後のconsole.logの結果を見て下さい。
var宣言前のconsole.logが変数宣言前なのにエラーにならない(暗黙的に変数宣言されたものとして扱われる)のが巻き上げです

var countUpFromZero = function () {
    console.log(count);  // undefind  
    var count = 0;
    console.log(count);  // 0
return function(){
    return ++count;
}
}();

console.log(countUpFromZero()) //1
console.log(countUpFromZero()) //2
console.log(countUpFromZero()) //3

こちらはオライリーさんのJavascriptパターンP14 2.2.2巻き上げに記載あります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/23 11:48

    ご回答ありがとうございます。
    var countUpFromZero = function(){
    return function(){
    return ++count;
    }
    }();

    console.log(countUpFromZero()) //Nan
    var count = 0
    こうすることで理解できたように思います。

    キャンセル

checkベストアンサー

+1

 以下のソースを動かしながら考えてみて下さい。答えお待ちしてます。

…えらそ〜な事を書きましたが、自分も理解しきっていないかもしれないです。
有意識者の方、ツッコミお待ちしてます。

 Q.なぜクロージャは、値を保持できるのか?

Q1:countUpFromZeroに何が入っていますか?
Q2:countUpFromZeroにはcountが無いからどこを参照していますか?
Q5:countUpでは、countの値が保持できないのは何故ですか?

 Q.クロージャのメリットは?

Q3:参照しているQ2のcountのスコープはグローバルですか?関数スコープですか?
Q4:Q3のスコープだと、countUpFromZero関数以外からアクセスは出来ますか?

var countUpFromZero = function () {
    var count = 0;
    return function () {
        return ++count;
    }
}();

console.log(countUpFromZero.toString());
console.log(countUpFromZero());
console.log(countUpFromZero());
//count++;
console.log(countUpFromZero());

var countUp = function () {
    var count = 0;
    return (++count);
};

console.log(countUp());
console.log(countUp());
//count++;
console.log(countUp());

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/23 16:55

    Q1 countUpFromZero には 無名関数と変数countが入っています
    Q2 無名関数が定義されたとき、"return ++count" のcountはcountUpFromZero内の変数countを参照する。そして無名関数が呼び出された際にもそれを参照し続けるのでcountがインクリメントされ続ける。
    Q3 countのスコープは関数スコープ
    Q4できない
    Q5 ここが理解できていないところだと思います。countUpの場合、呼び出された際に"var count = 0"となっておいるため、毎回countの値が0に戻され、結果として1が返されるのでは、と考えました。
    ただその理屈で行くと、countUpFromZeroにおいても毎回countの値は0に戻されてしまいます。
    また、countUpFromZero.toString // function () {
    return ++count;
    }
    とのことですが、var count = 0はどこにいったのでしょうか?あまりに初歩的な質問でしたら、申し訳ありません。

    キャンセル

  • 2018/06/23 17:40

    Q1 countUpFromZero には 無名関数と変数countが入っています

    以下の結果を見てみました?
    console.log(countUpFromZero.toString());
    →count有りました?

    Q2 無名関数が定義されたとき、"return ++count" のcountはcountUpFromZero内の変数countを参照する。そして無名関数が呼び出された際にもそれを参照し続けるのでcountがインクリメントされ続ける。

    正解です。関数宣言時にスコープチェーンが決定します。
    countUpFromZero(内容はfunction (){return ++count;})が有る限り、countへの参照が続きます。

    Q3 countのスコープは関数スコープ
    Q4できない
    正解です。関数スコープなので関数より上のスコープ階層からは操作されなくなります。
    逆にいうと、countを宣言している関数に内包されている子供な関数関数からは
    スコープチェーンにより参照可能です。
    →countUpFromZero(内容はfunction (){return ++count;})にはcountが無いけど、
     上位のスコープに有るcountを参照している。上位の関数は終了したけどcountへの参照は
     続いているので、count(の値)は保持される。

    Q5 ここが理解できていないところだと思います。countUpの場合、呼び出された際に"var count = 0"となっておいるため、毎回countの値が0に戻され、結果として1が返されるのでは、と考えました。
    ただその理屈で行くと、countUpFromZeroにおいても毎回countの値は0に戻されてしまいます。
    また、countUpFromZero.toString // function () {
    return ++count;
    }
    とのことですが、var count = 0はどこにいったのでしょうか?あまりに初歩的な質問でしたら、申し訳ありません。

    実際の動きを見たほうが理解しやすいです。
    1.Q1で何を間違っているか確認しましょう。
    2.以下の行の各行に付けれるところはすべてブレークポイント付けてデバッグ実行してみて下さい。
     `var count = 0で毎回countの値は0にならないのは何故?`について分かると思います。

    ```JS
    var countUpFromZero = function parent() {
    var count = 0;
    return function childen() {
    return ++count;
    }
    }();

    console.log(countUpFromZero());
    ```

    キャンセル

  • 2018/06/24 08:49

    ご返信が遅くなりました。
    ご解説ありがとうございます。
    Q5についてなのですが、
    まず
    var countUpFromZero のparent関数が即時実行され、
    var countUpFromZero = function children (){
    return ++count;
    }
    になると思います。
    var count = 0の下にブレークポイントをつけても、二回目以降は反応しないことを確認しました。
    というこはparentのもっている関数スコープの変数countはどこかに隠ぺいされているものの、その存在を保っている、ということでしょうか?

    キャンセル

  • 2018/06/24 11:26

    > parentのもっている関数スコープの変数countはどこかに隠ぺいされているものの、その存在を保っている、ということでしょうか?
    countUpFromもcountUpZeroも関数抜けたのに、
    countUpFromZeroだけcountの値が保持するのはなぜか?

    var countUp = function () {
    var count = 0;
    return (++count);
    };

    countUp関数が終わるとcountを参照するものがないので、countは破棄される

    var countUpFromZero = function parent() {
    var count = 0;
    return function childen() {
    return ++count;
    }
    }();

    countUpFromZero関数の中身はcountをreturnするchilden関数です。(countを参照する)
    だからparent関数にあるcount変数は、parent関数が終わっても破棄されません。(隠蔽されているわけではない)

    もしclassが分かるなら、以下と読み替えるとわかりやすいかな。
    closure=メソッドが1つだけのclass
    ↓ 

    var countUpFromZero = function constructor() {
    var count = 0;
    return function method() {
    return ++count;
    }
    }();

    キャンセル

  • 2018/06/26 18:54 編集

    すみません、遅くなりました。
    ご返信をしたと思い込んでおりまして...
    ご解説まことにありがとうございました。
    多くのことを学ばせていただきました。
    また、よろしくお願いいたします。

    キャンセル

0

「開眼!JavaScript」 良書ですよね。

P98 7.4スコープチェーン を見ましょう。

var countUpFromZero = function(){
     return function(){
         return ++count;
     }
}();
var count = 0;    // ここの値を変えてみる


ここにcountが宣言されていないので、上のスコープの階層にcountを探しに行きます。


var count = 0;の値を変えたらどうなるかわかりやすいですよ。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/23 11:50

    ありがとうございます。
    私はまだ「二分開き」といった感じですが、頑張って内容をモノにしたいです。
    教えていただいた部分をしっかりと確認しておきます。

    キャンセル

0

ちなみにBehemothさんが書き換えた物(下記countUpFromZero_nonClosure)はクロージャの動作をしていません。以下を動かしてみると(エラーでて動かない所有ります。削ってみましょう)動きが解ると思います

var countUpFromZero = function () {
    var count = 0;
return function(){
    return ++count;
}
}();

console.log(countUpFromZero()) //1
console.log(countUpFromZero()) //2
count++; // count is not defind
console.log(countUpFromZero()) //3



var countUpFromZero_nonClosure = function () {
    return function(){
        return ++count2;
    }
}();
var count2 = 1;

console.log(countUpFromZero_nonClosure()) //2
console.log(countUpFromZero_nonClosure()) //3
count2++;   //count2が関数外から変更できている
console.log(countUpFromZero_nonClosure()) //4?5?試してみて下さい

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/23 12:08

    回答、ありがとうございます!
    クロージャーのポイントは「関数スコープ内で宣言された変数を参照し続けること(生成された環境を記憶している)」にある、ということでしょうか?
    また、countUpFromZero_nonClosureは、クロージャーを生成するわけではなく、単なるcountという名前の変数の値をインクリメントする関数、ということですか?

    キャンセル

  • 2018/06/23 15:47

    理解しきれていない部分が有ると思いましたので、別回答でソースを書きました。
    そちらを見て下さい。

    キャンセル

  • 2018/06/23 16:40

    承知しました。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    14789questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。