深刻挖掘js之做用域閉包

前提:JavaScript中閉包無處不在,你只須要可以識別並擁有它。閉包是基於詞法做用域書寫代碼時天然產生的結果。閉包

1、實質問題

  • 當函數能夠記住並訪問所在的詞法做用域是,就產生了閉包。有的人會很好奇,什麼是詞法做用域,接下來我給你們普及一下什麼是詞法做用域。

詞法做用域

簡單的來講詞法做用域就是定義在詞法階段的做用域,換就話說,詞法做用域是由你在寫代碼時將變量和塊做用域寫在哪裏來決定的app

function foo(a){
    var b = a*2;
    function bar(c){
        console.log(a,b,c);
    }
    bar (b*3);
}
foo(2);

在這個例子中包含了三個逐級嵌套的做用域異步

  • 一、包含整個全局做用域,foo
  • 二、包含着foo所建立的做用域,a , bar , b
  • 三、包含着bar所穿件的做用域 ,c

關於詞法做用域咱們就現講這麼多,接下來仍是回到咱們的正文,做用域閉包函數

function foo(){
    var a=2;
    function bar(){//bar()的詞法做用域可以訪問foo()的內部做用域
        console.log(a);
    }
    return bar;//將bar()函數當作一個值類型進行傳遞
}
var baz =foo();
baz(2);

foo()內部做用域依然存在,沒有被回收。bar()依然持有該做用域的引用。這個引用就叫閉包工具

function foo(){
    var a=2;
    function baz(){
        console.log(a);
    }
    bar(baz);
}

function bar(fn){
    fn();
}
foo();
//把內部函數baz傳遞給bar,
// 當調用這個內部函數,
// 他涵蓋的foo()內部做用域的閉包就能夠觀察到了,由於它可以訪問a
var fn;
function foo(){
    var a =2;
    function baz(){
        console.log(a);
    };
    fn = baz;
}

function bar(){
    fn();
}
foo();
bar();
  • 不管經過何種手段將內部函數傳遞到所在的詞法做用域之外,他都會持有對原始定義做用域的引用,不管在何處執行這個函數都會使用閉包。

2、提高

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

在引擎內部,內置的工具函數setTimeout()持有對一個參數的引用,引擎會調用這個函數,在這個例子中就是內部的timer函數,而詞法做用域就在這個過程當中保持完整。這就是閉包。code

3、循環和閉包

for(var i=0;i<=5;i++){
    setTimeout(function timer() {
        console.log(i);
    }, i*1000);
}
//你們猜猜結果會是啥?

正常狀況下會分別輸出數字1~5,但實際會輸出五次6。ip

  • 延遲函數的回調會在循環結束時才執行。能夠想象一下異步加載機制。所以settimeout每次要等到循環結束後才顯示值,這樣就獲得了咱們的結果,輸出了五次6。

代碼中有什麼缺陷致使它的行爲通語義所暗示的不一致呢?
咱們須要更多的做用域,特別是在循環的過程當中每一個迭代都要一個閉包做用域,所以想到了當即執行函數作用域

for( var i=0;i<=5;i++){
    (function(){
        setTimeout(function timer() {
        console.log(i);
    }, i*1000);
    })();
}

這樣子爲何還不行呢?咱們顯然擁有了更過的詞法做用域。
每一個延遲函數都會講IIFE在每次迭代中建立的做用域封閉起來。get

  • 若是做用域是空的話,咱們的IIE只是一個什麼都沒用的空做用域。
for( var i=1;i<=5;i++){
    (function(){
        var j =i;
        setTimeout(function timer() {
        console.log(j);
    }, j*1000);
    })();
}

重返塊做用域

for(let i =1;i<=5;i++){
    
    setTimeout(function timer() {
        console.log(i);
    }, i*1000);
}

let欺騙此法做用域,每次在迭代都去建立一個新的做用域,而後執行完後被銷燬,這樣每一個迭代都有本身的做用域就能夠達到咱們的預期效果,輸出1~5。it

4、模塊

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.doAnother();
foo.doSomething();

這個模式JavaScript中被稱爲模塊,保護私有屬性,只提供公共方法。

  • 模塊模式須要具有兩個必要條件:
  • 一、必須有外部的封閉函數
  • 二、封閉函數必須返回至少一個內部函數

現代的模塊機制

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

var MyModules = (function Manager(){
            var modules = {};
            function define(name,deps,impl){
                for(var i=0;i<deps.length;i++){
                    deps[i] = module[deps[i]];
                }
                modules[name] = impl.apply(impl,deps);
            }
            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("hippo"));

foo.awesome();
相關文章
相關標籤/搜索