JS-------作用域和閉包

一盅抚、大家可以先看一個例子

function getName() {
  var name = "美女的名字";
  console.log(name);     //"美女的名字"
}
function displayName() {
    console.log(name);  //報錯
}

但是為了得到美女的名字漠魏,不死心的單身汪把代碼改成了這樣:

function getName() {
  var name = "美女的名字";
  function displayName() {
    console.log(name);   
  }
  return displayName;
}
var 美女 = getName();  
美女()  //"美女的名字"

這下,美女是一個閉包了妄均,單身汪想怎么玩就怎么玩了借尿。(但并不推薦單身汪用中文做變量名的寫法斤蔓,大家不要學(xué))。
這樣的例子該如何理解,我們就需要討論一下作用域了真竖。

二贯钩、變量的作用域

變量的作用域指的是宣虾,變量起作用的范圍骤宣。也就是能訪問到變量的有效范圍。

JavaScript的變量依據(jù)作用域的范圍可以分為:

  • 全局變量
  • 局部變量

2.1 全局變量

==定義在函數(shù)外部的變量都是全局變量寄症。==

全局變量的作用域是==當(dāng)前文檔==宙彪,也就是當(dāng)前文檔所有的JavaScript腳本都可以訪問到這個變量。

下面的代碼是書寫在同一個HTML文檔中的2個JavaScript腳本:

<script type="text/javascript">
    //定義了一個全局變量有巧。那么這個變量在當(dāng)前html頁面的任何的JS腳本部分都可以訪問到释漆。
    var v = 20; 
    alert(v); //彈出:20
</script>
<script type="text/javascript">
    //因為v是全局變量,所以這里仍然可以訪問到篮迎。
    alert(v);  //彈出:20
</script>

再看下面一段代碼 :

<script type="text/javascript">
    alert(a);
    var a = 20;
</script>

運行這段代碼并不會報錯男图, alert(a); 這行代碼彈出:undefined。

為什么在聲明 a 之前可以訪問變量 a 呢? 能訪問 a 為什么輸出是undefined而不是20呢柑潦?

==聲明提前享言!==

  • 所有的全局變量的聲明都會提前到JavaScript的前端聲明。也就是所有的全局變量都是先聲明的渗鬼,并且早于其他一切代碼。
  • 但是變量的賦值的位置并不會變荧琼,仍然在原位置賦值譬胎。

所以上面的代碼等效下面的代碼:

<script type="text/javascript">
    var a; //聲明提前
    alert(a);
    a = 20; //賦值仍然在原來的位置
</script>

2.2 局部變量

在函數(shù)內(nèi)聲明的變量差牛,叫局部變量!表示形參的變量也是局部變量堰乔!

局部變量的作用域是局部變量所在的整個函數(shù)的內(nèi)部偏化。 在函數(shù)的外部不能訪問局部變量。

<script type="text/javascript">
    function f(){
        alert(v);  //   彈出:undefined
        var v = "abc";  // 聲明局部變量镐侯。局部變量也會聲明提前到函數(shù)的最頂端侦讨。
        alert(v);   //  彈出:abc
    }
    alert(v);  //報錯。因為變量v沒有定義苟翻。 方法 f 的外部是不能訪問方法內(nèi)部的局部變量 v 的韵卤。
 </script>

2.3 全局變量和局部變量的一些細節(jié)

看下面一段代碼:

<script type="text/javascript">
    var m = 10;
    function f(){
        var m = 20;
        alert("方法內(nèi)部:" + m);  //代碼1
    }
    f();
    alert("方法外部:" + m); //代碼2
</script>

在方法內(nèi)部訪問m,訪問到的是哪個m呢崇猫?局部變量的m還是全局變量的m沈条?

2.3.1 全局變量和局部變量重名問題

  1. 在上面的代碼中,當(dāng)局部變量與全局變量重名時诅炉,局部變量的作用域會覆蓋全局變量的作用域蜡歹。也就是說在函數(shù)內(nèi)部訪問重名變量時,訪問的是局部變量涕烧。==所以 "代碼1" 部分輸出的是20月而。==
  2. 當(dāng)函數(shù)返回離開局部變量的作用域后,又回到全局變量的作用域议纯。==所以代碼2輸出10景鼠。==
  3. 如何在函數(shù)訪問同名的全局變量呢?==通過:window.全局變量名==
<script type="text/javascript">
    var m = 10;
    function f(){
        var m = 20;
        alert(window.m);  //訪問同名的全局變量痹扇。其實這個時候相當(dāng)于在訪問window這個對象的屬性铛漓。
    }
    f();  
</script>

2.3.2 JavaScript中有沒有塊級作用域?

看下面一段代碼:

<script type="text/javascript">
  var m = 5;
  if(m == 5){
    var n = 10;
  }
  alert(n); //代碼1
</script>

代碼1輸出什么鲫构? undefined還是10浓恶?還是報錯?

==輸出10结笨!==

  • JavaScript的作用域是按照函數(shù)來劃分的
  • ==JavaScript沒有塊級作用域==

在上面的代碼中包晰,變量 n 雖然是在 if 語句內(nèi)聲明的,但是它仍然是全局變量炕吸,而不是局部變量伐憾。

只有定義在方法內(nèi)部的變量才是局部變量

注意:

  • 即使我們把變量的聲明放在 if、for等塊級語句內(nèi)赫模,也會進行==聲明提前==的操作树肃!

三、作用域鏈---作用域的深入理解

3.1 執(zhí)行環(huán)境

? 執(zhí)行環(huán)境( execution context )是 JavaScript 中最為重要的一個概念瀑罗。執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)胸嘴,決定了它們各自的行為雏掠。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的 變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中劣像。雖然我們編寫的代碼無法訪問這個對象乡话,但解析器在處理數(shù)據(jù)時會在后臺使用它。

? 全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境耳奕。在 Web 瀏覽器中绑青,全局執(zhí)行環(huán)境被認為是 window 對象,因此所有全局變量和函數(shù)都是作為 window 對象的屬性和方法創(chuàng)建的屋群。對全局執(zhí)行環(huán)境變量來說闸婴,變量對象就是window對象,對函數(shù)來說谓晌,變量對象就是這個函數(shù)的 活動對象 掠拳,活動對象是在函數(shù)調(diào)用時創(chuàng)建的一個內(nèi)部變量。

? 每個函數(shù)都有自己的執(zhí)行環(huán)境纸肉,當(dāng)執(zhí)行流進入一個函數(shù)時溺欧,函數(shù)的執(zhí)行環(huán)境就會被推入一個執(zhí)行環(huán)境棧中。而在函數(shù)執(zhí)行之后柏肪,棧將執(zhí)行結(jié)束的函數(shù)的執(zhí)行環(huán)境彈出姐刁,把控制權(quán)返回給之前的執(zhí)行環(huán)境。

3.2 作用域鏈

? 作用域鏈與一個執(zhí)行環(huán)境相關(guān)烦味,作用域鏈用于在標(biāo)示符解析中變量查找聂使。

? 在JavaScript中,函數(shù)也是對象谬俄,實際上柏靶,JavaScript里一切都是對象。函數(shù)對象和其它對象一樣溃论,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內(nèi)部屬性屎蜓。其中一個內(nèi)部屬性是[[Scope]],由ECMA-262標(biāo)準(zhǔn)第三版定義钥勋,他就指向了這個函數(shù)的作用域鏈炬转。作用域鏈中存儲的是與每個執(zhí)行環(huán)境相關(guān) **變量對象 **(函數(shù)內(nèi)部也是活動對象)。

? 當(dāng)創(chuàng)建一個函數(shù)( 聲明一個函數(shù) )后算灸,那么會創(chuàng)建這個函數(shù)的作用域鏈扼劈。這個函數(shù)的作用域鏈在這個時候只包含一個變量對象(window)

<script type="text/javascript">
    function sum(num1, num2){
        var sum = num1 + num2;
        return sum;
    }
</script>

函數(shù) sum 的作用域鏈?zhǔn)疽鈭D:

說明:

  • 函數(shù)創(chuàng)建的時候,這個時候作用域鏈中只有一個 變量對象 (window)

當(dāng)執(zhí)行下面的代碼:

<script type="text/javascript">
    function sum(num1, num2){
        var sum = num1 + num2;
        return sum;
    }
    var sum = sum(3, 4);
</script>
當(dāng)調(diào)用 sum 函數(shù)時菲驴,會首先創(chuàng)建一個 **“執(zhí)行環(huán)境”**荐吵,這個 **執(zhí)行環(huán)境** 有自己的作用域鏈,這個作用域鏈初始化為 sum 函數(shù)的 [[scope]] 所包含的對象。然后創(chuàng)建一個 與這個執(zhí)行環(huán)境相關(guān)的 **變量對象( 活動對象 )** 捍靠,這個 **變量對象** 中存儲了在這個函數(shù)中定義的所有參數(shù)沐旨、變量和函數(shù)森逮。把 **變量對象** 存儲在作用域中的頂端榨婆。  以后在查找變量的時候,總是從作用域鏈條的頂端開始查找褒侧,一直到作用域鏈條的末端良风。

看下面的示意圖:

說明:

  1. 在sum中訪問一個變量的時候,總是從作用域鏈的頂端開始查找闷供,如果找到就得到結(jié)果烟央,如果找到不到就一直查找,直到作用域鏈的末端歪脏。
  2. 因為在方法內(nèi)的存在變量和函數(shù)的聲明提前現(xiàn)象疑俭,所以函數(shù)一旦執(zhí)行 函數(shù)的活動對象(變量對象)中總是保存了這個韓碩中聲明的所有變量和函數(shù)。
  3. 如果在函數(shù)中又定義了一個內(nèi)部函數(shù)(還沒有執(zhí)行)婿失,則這個時候內(nèi)部函數(shù)的作用域钞艇,是包含了外部函數(shù)的作用域。 一旦內(nèi)部函數(shù)開始執(zhí)行則把自己的活動對象添加到了這個作用域的頂端豪硅。
<script type="text/javascript">
    function sum(num1, num2){
        var sum = num1 + num2;
        function inner (a) {
            
        }

        return sum;
    }

    var sum = sum(3, 4);
</script>

內(nèi)部函數(shù)的作用域:

函數(shù)執(zhí)行后的作用域示意圖不再畫出哩照。

四、閉包

看下面的代碼:

<script type="text/javascript">
    function createSumFunction(num1, num2){
        return function () {
            return num1 + num2;
        };
    }

    var sumFun = createSumFunction(3, 4);
    var sum = sumFun();
    alert(sum);
</script>

? 在上面的代碼中懒浮,createSumFunction函數(shù)返回了一個匿名函數(shù)飘弧,而這個匿名函數(shù)使用了createSumFunction函數(shù)中的局部變量(參數(shù)),即使createSumFunction這個函數(shù)執(zhí)行結(jié)束了砚著,由于作用域鏈的存在次伶,他的局部變量在匿名函數(shù)中仍然可以使用,這個匿名函數(shù)就是閉包稽穆。

? 閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)冠王。

? 閉包是一種特殊的對象。它由兩部分構(gòu)成: 函數(shù)秧骑,以及創(chuàng)建該函數(shù)的環(huán)境 版确。環(huán)境由閉包創(chuàng)建時在作用域中的任何局部變量組成。在我們的例子中乎折,sumFun 是一個閉包绒疗,由 匿名 函數(shù)和閉包創(chuàng)建時存在的num1num2 兩個局部變量組成。

五骂澄、閉包的應(yīng)用

5.1 返回外部函數(shù)的局部變量

<script type="text/javascript">
    function outer () {
        var num = 5;
        //定義一個內(nèi)部函數(shù)
        function inner () {
            //內(nèi)部函數(shù)的返回值是外部函數(shù)的一個局部變量
            return num;
        }
        //把局部變量的值++
        num++;
        // 返回內(nèi)部函數(shù)
        return inner;
    }
    var num = outer()();  // 6
    alert(num);  
</script>

說明:

  1. 這例子中吓蘑,雖然函數(shù)的聲明在num++之前,但是函數(shù)返回的時候num已經(jīng)++過了,所以只是num自增之后的值磨镶。
  2. 結(jié)論:閉包中使用的局部變量的值溃蔫,一定是局部變量的最后的值。

5.2 使用函數(shù)自執(zhí)行和閉包封裝對象

封裝一個能夠增刪改查的對象

<script type="text/javascript">
    var person = (function () {
        //聲明一個對象琳猫,增刪改查均是針對這個對象
        var personInfo = {
            name : "李四",
            age : 20
        };
        //返回一個對象伟叛,這個對象中封裝了一些對personInfor操作的方法
        return {
            //根據(jù)給定的屬性獲取這個屬性的值
            getInfo : function (property) {
                return personInfo[property];
            },
            //修改一個屬性值
            modifyInfo : function (property, newValue) {
                personInfo[property] = newValue;
                
            },
            //添加新的屬性
            addInfo : function (property, value) {
                personInfo[property] = value;
                
            },
             //刪除指定的屬性
            delInfo : function (property) {
                delete personInfo[property];
                
            }
        }
    })();
    alert(person.getInfo("name"));
    person.addInfo("sex", "男");
    alert(person.getInfo("sex"));
</script>

5.3 for循環(huán)典型問題

看下面的代碼

<body>
    <input type="button" value="按鈕1"    >
    <input type="button" value="按鈕2"    >
    <input type="button" value="按鈕3"    >
    <script type="text/javascript">
        var btns = document.getElementsByTagName("input");
        for (var i = 0; i < 3; i++) {
            btns[i].onclick = function () {
                alert("我是第" + (i + 1) + "個按鈕");
            };
        }
    </script>
</body> 

發(fā)現(xiàn)在點擊三個按鈕的時候都是彈出 我是第4個按鈕。 為什么呢脐嫂?閉包導(dǎo)致的统刮! 每循環(huán)一次都會有一個匿名函數(shù)設(shè)置點擊事件,閉包總是保持的變量的最后一個值账千,所以點擊的時候侥蒙,總是讀的是 i 的組后一個值4.

解決方案1:給每個按鈕添加一個屬性,來保存 每次 i 的臨時值

<body>
    <input type="button" value="按鈕1"    >
    <input type="button" value="按鈕2"    >
    <input type="button" value="按鈕3"    >
    <script type="text/javascript">
        var btns = document.getElementsByTagName("input");
        for (var i = 0; i < 3; i++) {
            //把i的值綁定到按鈕的一個屬性上匀奏,那么以后i的值就和index的值沒有關(guān)系了鞭衩。
            btns[i].index = i;
            btns[i].onclick = function () {
                alert("我是第" + (this.index + 1) + "個按鈕");
            };
        }
    </script>
</body>

解決方案2:使用匿名函數(shù)的自執(zhí)行

<body>
    <input type="button" value="按鈕1"    >
    <input type="button" value="按鈕2"    >
    <input type="button" value="按鈕3"    >
    <script type="text/javascript">
        var btns = document.getElementsByTagName("input");
        for (var i = 0; i < 3; i++) {   
            //因為匿名函數(shù)已經(jīng)執(zhí)行了,所以會把 i 的值傳入到num中娃善,注意是i的值论衍,所以num
            (function (num) {
                btns[i].onclick = function () {
                    alert("我是第" + (num + 1) + "個按鈕");
                }
            })(i);
        }
    </script>
</body>

相信通過上面的例子說明,大家對什么是作用域和閉包已經(jīng)有了更深層次的了解会放。那么大家來看看下面的2個例子打印的結(jié)果是什么饲齐?

var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咧最,隨后出現(xiàn)的幾起案子捂人,更是在濱河造成了極大的恐慌,老刑警劉巖矢沿,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滥搭,死亡現(xiàn)場離奇詭異,居然都是意外死亡捣鲸,警方通過查閱死者的電腦和手機瑟匆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栽惶,“玉大人愁溜,你說我怎么就攤上這事⊥獬В” “怎么了冕象?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長汁蝶。 經(jīng)常有香客問我渐扮,道長论悴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任墓律,我火速辦了婚禮膀估,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耻讽。我一直安慰自己察纯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布齐饮。 她就那樣靜靜地躺著捐寥,像睡著了一般笤昨。 火紅的嫁衣襯著肌膚如雪祖驱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天瞒窒,我揣著相機與錄音捺僻,去河邊找鬼。 笑死崇裁,一個胖子當(dāng)著我的面吹牛匕坯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拔稳,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼葛峻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巴比?” 一聲冷哼從身側(cè)響起术奖,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轻绞,沒想到半個月后采记,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡政勃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年唧龄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奸远。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡既棺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懒叛,到底是詐尸還是另有隱情丸冕,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布芍瑞,位于F島的核電站晨仑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洪己,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一妥凳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧答捕,春花似錦逝钥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沃琅,卻和暖如春哗咆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背益眉。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工晌柬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人郭脂。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓年碘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親展鸡。 傳聞我的和親對象是個殘疾皇子屿衅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容