閉包

?? 本文分為(chang)幾(pian)個(da)部(lun)分

  • ?? 作用域鏈(Scope chain)
  • ?? 閉包(Closure)的定義
  • ?? 閉包(Closure)的使用

??最開始學(xué)習(xí)前端的時候,總是聽到一個很新奇的詞叫閉包??傆X得這個閉包聽起來很高大上,當(dāng)時也看了很多,還是覺得模糊不清,直到當(dāng)時看到了一句話,豁然開朗。

??“JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里。

??接下來就從這點(diǎn)出發(fā),來看下怎么理解閉包。

作用域鏈(Scope chain)


??首先:
??要理解 作用域鏈 (Scope chain) 首先要理解 作用域 (Scope)是什么;

  • ??作用域:在JavaScript中,作用域是執(zhí)行代碼的上下文。

  • ?? 作用域有三種類型:
    ?? 全局作用域 :在代碼中任何地方 都能訪問到。
    ?? 局部作用域(函數(shù)作用域) :只在固定的代碼片段中可訪問到。
    ?? eval作用域 : 與調(diào)用eval的方式相關(guān)聯(lián)。

  • ??關(guān)于JavaScript中的塊級作用域
    ??JavaScript中沒有塊級作用域,邏輯語句 ( if(){} ) 和 ( for(){} ) 無法創(chuàng)建作用域。所以變量可以相互覆蓋。這里可以看一個例子

    var a = 1; //定義一個全局變量a 并且初始化為1
    if(true){
        a = 3;//在if語句中 給 變量 a 賦值,因?yàn)閕f無法創(chuàng)建塊級作用域 ,所以這里訪問到的是全局變量a
        for(var i = 0 ; i < 3 ; i++ ){
            a = i;//在for語句中給a賦值 因?yàn)閒or無法創(chuàng)建塊級作用域,所以這里訪問到的也是全局變量a
        }       
    }
  • 關(guān)于JavaScript中聲明變量時使用的var
    //在a函數(shù)中 我們聲明了一個變量var
    var a = function(){
        s = 2;//如果s的作用域是函數(shù)作用域,那么我們在函數(shù)外打印它,結(jié)果是undefined 因?yàn)檎也坏?    }
    console.log(s);//在控制臺打印出2

??JavaScript 會將 缺少var的變量聲明的變量 聲明在全局中。
??上面的例子如果我們在s前面加一個var 那么 就會打印出undefined 因?yàn)榇藭rs在用var聲明時,它的作用域變成了函數(shù)a的作用域。

??很好理解對吧。
??那么當(dāng)一個函數(shù)被執(zhí)行時,JavaScript會去查找與變量關(guān)聯(lián)的值,這個查找的過程會遵循一個規(guī)則:

??這個規(guī)則,就是作用域鏈。

??可以通過一個例子來理解什么是作用域鏈。

    var sthToAlert = 'peace peace'//首先定義了一個變量sthToAlert 并初始化

    var func_1 = function(){//定義第一層函數(shù)func_1

        var func_2 = function(){//定義第二層函數(shù)func_2
            console.log(sthToAlert);//在func_2中打印變量sthToAlert 結(jié)果是 'peace peace'
        }

    }

??代碼很簡單,但是能說明很多問題。
??首先,JavaScript在執(zhí)行這段代碼的時候,它執(zhí)行到console.log(sthToAlert); 時,會發(fā)現(xiàn),嗯?這個值哪里冒出來的。
??然后它會 由內(nèi)而外 的去尋找sthToAlert的定義。

??順序也就是 檢查func_2 中是否有sthToAlert的定義 ?????→
??檢查func_2的父函數(shù)func_1 中是否有sthToAlert的定義??→
??檢查func_1的父函數(shù)中是否有sthToAlert的定義
??···
??檢查全局作用域內(nèi)中是否有sthToAlert的定義(如果沒找到 就會返回undefined)

??那么 如果我們同時在全局和func_2中 同時定義了 sthToAlert 會怎么樣呢?
??JavaScript會找到就近的定義(func_2中sthToAlert的定義),一旦找到了關(guān)于sthToAlert的定義,就不再找下去了。

閉包(Closure)的定義


??現(xiàn)在我們知道了作用域鏈的概念,那我們回頭再來看最開始的這句話:

??“JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里。

??換句話說,函數(shù)的作用域鏈 是根據(jù) 函數(shù)定義時的位置 決定的
??而不是在調(diào)用時確定的。這叫做 "詞法作用域"

??那么這和閉包又有什么關(guān)系呢?
??可以結(jié)合一個場景來看:

    //我們定義了一個person 函數(shù)
    var person = function(){
        //在函數(shù)的內(nèi)部 我們定義了這個person 的birth 
        //但是我們不想讓別人一下就知道這個人的 birth的信息 
        //所以我們用了一個 var 來定義這個變量
        var birth = "June/18";
    }
     //我們很想打印這個birth  
    //但是又因?yàn)閎irth在定義時 是person函數(shù)作用域,所以在外面訪問不到
    //只能返回undefined
    console.log(birth);

??那么這時候我們怎么解決這個問題呢?
??答案就是用閉包來解決。
??怎么做呢?修改下代碼

    //我們定義了一個person 函數(shù)
    var person = function(){
        //在函數(shù)的內(nèi)部 我們定義了這個person 的birth 
        //但是我們不想讓別人一下就知道這個人的 birth的信息 
        //所以我們用了一個 var 來定義這個變量
        var birth = "June/18";

        //為了能在外部訪問到birth的值,我們返回一個匿名函數(shù)來做這件事情。
        return function(){
            console.log(birth);
        }

    }
    //我們只需要調(diào)用person返回的匿名函數(shù)就可以達(dá)到效果
    person()();

??現(xiàn)在已經(jīng)簡單的說明了閉包的概念。

閉包(Closure)的使用


??在說到閉包的使用之前,可以先看一個例子。

        //定義了一個函數(shù)Q 這個函數(shù)接收一個string類型的參數(shù)
        var Q = function (string){
            //把傳入的參數(shù)賦值給當(dāng)前對象的status屬性
            this.status = string;
        };

        //注意這里沒有通過new關(guān)鍵字調(diào)用
        console.log((Q('111')).status);
        //這里返回的是:Uncaught TypeError: Cannot read property 'status' of undefined
        
        //注意這里是通過new關(guān)鍵字調(diào)用
        console.log((new Q('111')).status);
        //這里返回的是:111

??這個例子很有意思,在函數(shù)調(diào)用的方式不一樣會直接導(dǎo)致結(jié)果的不一樣。
??因?yàn)樵贘avaScript中我們直接調(diào)用一個函數(shù)時,這個 this 會綁定到 全局對象 。
??然后就很好理解為什么提示 'status' 屬性的 undefined 因?yàn)檫@里的this指向全局對象this

??在通過new調(diào)用時,那么實(shí)際上JavaScript做的事情是,它會再創(chuàng)建一個鏈接到該函數(shù)的prototype成員的新對象,并且把this指向這個新對象。同時在函數(shù)調(diào)用結(jié)束,會把這個對象返回。
用new關(guān)鍵來調(diào)用函數(shù)的這種行為稱為:構(gòu)造器調(diào)用模式

??說了這么多,這和閉包有什么關(guān)系呢?

??如果說,我并不想讓其他調(diào)用者知道,這個函數(shù)里面的屬性是怎么樣定義的,而是提供一個公用的getter和setter 讓其他人來讀取或者修改這些私有屬性。這時候閉包就派上了大用場??匆粋€例子:

      //創(chuàng)建一個myObject對象,對象內(nèi)定義了一個匿名函數(shù)。
      var myObject = (function(){
            //在函數(shù)內(nèi)部定義一個屬性 name 因?yàn)楹瘮?shù)作用域的原因,這個屬性為私有。
            var name = 'dendi';
            //返回一個對象,對象本身包含了兩個函數(shù),一個getName() 一個setName()
            return{
                getName:function(){
                    return name;
                },
                setName:function(_name){
                     name = _name;
                }
            }
        });

      // 調(diào)用myObject來創(chuàng)建一個實(shí)例。
      var temObj = myObject();
      console.log(temObj.getName()); //輸出dendi
      temObj.setName('dendoink');
      console.log(temObj.getName());//輸出dendoink

??(結(jié)束啦。)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容