読者です 読者をやめる 読者になる 読者になる

Kyle Simpson「Advanced JavaScript」学習メモ(2)クロージャ

JavaScript クロージャ
  • クロージャ

関数が構文スコープ外で実行されても、自分自身のスコープを覚えている機能をクロージャを呼ぶ。

 

function foo() {
  var bar = "bar";
  
  function baz() {
    alert(bar);
  }
  
  bam(baz);
}

function bam(baz) {
  baz();  //"bar"
}

foo();

この例では、bazがスコープ外で呼び出されているが、構文スコープを覚えているので変数barにアクセスできる。

 

よくある誤り:

//shows 6, 6, 6, 6, 6
for(var i=1; i<=5; i++) {
  setTimeout(function(){
    console.log(i);
  }, i * 1000);
}

このコードは「1,2,3...」ではなく、ループ後の値(6)が5回表示される。関数はすべて同じグローバルスコープの変数iを参照しているからである。

//shows 1, 2, 3, 4, 5
for(var i=1; i<=5; i++) {
  (function(i){
    setTimeout(function(){
      console.log(i);
    }, i * 1000);
  })(i);
}

上記のようにIIFEにすることで解決できる。

"use strict"
//shows 1, 2, 3, 4, 5
for(let i=1; i<=5; i++) {
  setTimeout(function(){
    console.log(i);
  }, i * 1000);
}

またはletキーワードを使う。letは、単純にforループ内のスコープに変数をつくるのではなく、forループのイテレーションごとにiを束縛する(イテレーションごとに別変数を作って値を束縛しているのと同じ)。

 

 

  •  モジュールパターン

 

クラシックモジュールパターン

モジュール全体を関数でラップして実行し、公開関数をキーとともに返す。次の例では、barが外部への公開APIになっている。

var foo = (function() {
  var o = { bar: "bar" };
  
  return {
    bar: function(){
      console.log(o.bar);
    }
  }
  
})();

foo.bar();  //"bar"

 

修正モジュールパターン(Modified module pattern)

クラシックモジュールパターンとほぼ同じだが、公開APIの区別がより分かりやすい。

var foo = (function() {
  var publicAPI = {
    bar: function() {
      publicAPI.baz();
    },
    baz: function(){
      console.log("baz");
    }
  }
return publicAPI; })(); foo.bar(); //"baz"

 

モダンモジュールパターン

ライブラリによっては、クラシックモジュールパターンをラップするモジュールマネージャ関数を提供している。

define("foo", function(){
  var o = { bar: "bar" };
  
  return {
    bar: function(){
      console.log(o.bar);
    }
  };
});

 

Future/ES6+モジュールパターン

ES6ではモジュールを別ファイルにすることで実現できる。exportされた関数が該当モジュールの公開APIとなる。

foo.js

var o = { bar: "bar" };

export function bar() {
  return o.bar;
}

呼び出し元

import bar from "foo"
far();  //"bar"

module foo from "foo"
import * as foo from "foo" //最終仕様はこう変わったようだ foo.bar(); //"bar"

 

モジュールパターンの欠点

・実装を隠ぺいしてしまうため、テストがしづらい(と考える人もいる)

・インスタンスを作るたびにモジュールのコピーができる

var fooFactory = function() {
  var o = { bar: "bar" };
  
  return {
    bar: function(){
      console.log(o.bar);
    }
  };
};

var myFoo = fooFactory();
var yourFoo = fooFactory();

myFoo.bar();
yourFoo.bar();

上記のように使う場合である。ただし、ほとんどの場合はパフォーマンスに影響がでるほど多くのモジュールは作成しないため、問題にならない。