當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行,簡單上講,就是在一個函數中內部的函數。javascript
function foo() { var a = 2; function bar() { console.log( a ); } return bar; } var baz = foo(); baz(); // 2 —— 朋友,這就是閉包的效果。
那麼bar函數就是一個閉包,它能夠在做用域外被訪問。
各類閉包實例:java
function foo() { var a = 2; function baz() { console.log( a ); // 2 } bar( baz ); } function bar(fn) { fn(); // 媽媽快看呀,這就是閉包! }
function setupBot(name, selector) { $( selector ).click( function activator() { console.log( "Activating: " + name ); } ); } setupBot( "Closure Bot 1", "#bot_1" ); setupBot( "Closure Bot 2", "#bot_2" );
function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
本質上不管什麼時候何地,若是將函數看成第一級的值類型並處處傳遞,就會由閉包,在定時器、事件監聽器、Ajax請求、跨窗口通訊、Web Workers或者其餘的異步(或者同步)任務中,只要使用了回調函數,實際上就是閉包閉包
模塊實例:app
function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); // cool foo.doAnother(); // 1 ! 2 ! 3
這種模式在JavaScript中就是模塊。
最多見的實現模塊模式的方法一般被稱爲模塊暴露。
模塊模式所具有的兩個必要特色:異步
必須有外部的封閉函數,該函數至少被調用一次(每次調用都會產生新的模塊實例)。 封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,而且能夠訪問或者修改私有的狀態。
模塊模式的另外一個簡單但強大的用法是命名將要做爲公共API返回的對象。ide
var foo = (function CoolModule(id) { function change() { // 修改公共 API publicAPI.identify = identify2; } function identify1() { console.log( id ); } function identify2() { console.log( id.toUpperCase() ); } var publicAPI = { change: change, identify: identify1 }; return publicAPI; })( "foo module" ); foo.identify(); // foo module foo.change(); foo.identify(); // FOO MODULE
經過在模塊實例的內部保留對公共API對象的內部引用,能夠從內部對模塊實例進行修改,包括添加或刪除方法和屬性,以及修改他們的值。
現代的簡單模塊機制:函數
var MyModules = (function Manager() { var modules = {} function define(name, argus, func) { for (var i = 0; i < argus.length; i++) { argus[i] = modules[argus[i]] } modules[name] = func.apply(func,argus) } function get(name) { return modules[name] } return { define: define, get: get } })() MyModules.define("bar", [], function () { function hello(who) { return "Let me introduce: " + who; } return { hello: hello }; }); MyModules.define("foo", ["bar"], function (bar) { var hungry = "hippo"; function awesome() { console.log(bar.hello(hungry).toUpperCase()); } return { awesome: awesome }; }); var bar = MyModules.get("bar"); var foo = MyModules.get("foo"); console.log( bar.hello.call(undefined, "hippo") ); // Let me introduce: hippo foo.awesome.call(undefined); // LET ME INTRODUCE: HIPPO
"foo"和"bar"模塊都是經過一個返回公共API的函數來定義的,「foo」甚至接受「bar」的實例做爲依賴參數,並能相應的使用它。
它們符合前面列出的模塊模式的兩個特色:調用包裝了函數定義的包裝函數,而且將返回值做爲該模塊的API。code
ES6的模塊機制
ES6中爲模塊增長了一級語法支持。在經過模塊系統進行加載時,ES6會將文件看成獨立的模塊來處理。每一個模塊均可以導入其餘模塊或特定的API成員,同時也能夠導出自身的API成員。
重構以前的模塊得三個文件對象
function hello(who) { return "Let me introduce: " + who } export hello
import hello from "bar" var hungry = "hippo" function awesome() { console.log(hello(hungry).toUpperCase()) } export awesome
module foo from "foo" module bar from "bar" console.log(bar.hello("hippo")) foo.awesome
import方法:將一個模塊中的一個或多個API導入到當前做用域中,並綁定在一個變量上。
module方法:將整個模塊的API導入並綁定到一個變量上
export方法:將當前模塊的一個標識符導出爲公共API
模塊文件中的內容會被看成包含在做用域閉包中同樣來處理。事件