- クロージャ
関数が構文スコープ外で実行されても、自分自身のスコープを覚えている機能をクロージャを呼ぶ。
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();
上記のように使う場合である。ただし、ほとんどの場合はパフォーマンスに影響がでるほど多くのモジュールは作成しないため、問題にならない。