《你不知道的JavaScript(上)》筆記——做用域閉包

當函數能夠記住並訪問所在的詞法做用域時, 就產生了閉包, 即便函數是在當前詞法做用域以外執行。javascript

function wait(message) {
    setTimeout( function timer() {
        console.log( message );
    }, 1000 );
}

wait( "Hello, closure!" );

將一個內部函數( 名爲 timer) 傳遞給 setTimeout(..)。 timer 具備涵蓋 wait(..) 做用域的閉包, 所以還保有對變量 message 的引用。wait(..) 執行 1000 毫秒後, 它的內部做用域並不會消失, timer 函數依然保有 wait(..)做用域的閉包。
深刻到引擎的內部原理中, 內置的工具函數 setTimeout(..) 持有對一個參數的引用, 這個參數也許叫做 fn 或者 func, 或者其餘相似的名字。 引擎會調用這個函數, 在例子中就是內部的 timer 函數, 而詞法做用域在這個過程當中保持完整。這就是閉包。java

在定時器、 事件監聽器、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

首先, CoolModule() 只是一個函數, 必需要經過調用它來建立一個模塊實例。 若是不執行外部函數, 內部做用域和閉包都沒法被建立。
其次, CoolModule() 返回一個用對象字面量語法 { key: value, ... } 來表示的對象。 這個返回的對象中含有對內部函數而不是內部數據變量的引用。 咱們保持內部數據變量是隱藏且私有的狀態。 能夠將這個對象類型的返回值看做本質上是模塊的公共 API
這個對象類型的返回值最終被賦值給外部的變量 foo, 而後就能夠經過它來訪問 API 中的屬性方法, 好比 foo.doSomething()。異步

所以模塊模式須要具有兩個必要條件:ide

  1. 必須有外部的封閉函數, 該函數必須至少被調用一次(每次調用都會建立一個新的模塊
    實例)。
  2. 封閉函數必須返回至少一個內部函數, 這樣內部函數才能在私有做用域中造成閉包, 並
    且能夠訪問或者修改私有的狀態。

模塊模式另外一個簡單但強大的變化用法是, 命名將要做爲公共 API 返回的對象:函數

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 對象的內部引用, 能夠從內部對模塊實例進行修改, 包括添加或刪除方法和屬性, 以及修改它們的值。工具

現代的模塊機制

大多數模塊依賴加載器 / 管理器本質上都是將這種模塊定義封裝進一個友好的 API。code

var MyModules = (function Manager() {
    var modules = {};

    function define(name, deps, impl) {
        for (var i = 0; i < deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply(impl, deps);
    }

    function get(name) {
        return modules[name];
    }

    return {define: define, get: get};
})();

這段代碼的核心是 modules[name] = impl.apply(impl, deps)。 爲了模塊的定義引入了包裝函數(能夠傳入任何依賴), 而且將返回值, 也就是模塊的 API, 儲存在一個根據名字來管理的模塊列表中。對象

下面展現瞭如何用它來定義模塊:

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("hippo")); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO

"foo" 和 "bar" 模塊都是經過一個返回公共 API 的函數來定義的。 "foo" 甚至接受 "bar" 的示例做爲依賴參數, 並能相應地使用它。

相關文章
相關標籤/搜索