JS之閉包詳細解讀

来源:https://www.cnblogs.com/tangdiying/archive/2018/12/18/10136823.html
-Advertisement-
Play Games

閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。 1.變數作用域 全局變數:所有的函數外部定義的變數,它的作用域是整個script。 局部變數:定義在函數體內部的變數,作用域僅限於函數體內部。離開函數體就會無效。再調用就是出錯。 舉例如下-局部變數: a變數定義在fun函數內,是局 ...


閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。

1.變數作用域

全局變數:所有的函數外部定義的變數,它的作用域是整個script。

局部變數:定義在函數體內部的變數,作用域僅限於函數體內部。離開函數體就會無效。再調用就是出錯。

舉例如下-局部變數:

<script type="text/javascript">
    function fun(){
        var a = 100;
    }
    console.log(a);
</script>

a變數定義在fun函數內,是局部變數,所以它不能在外部被訪問。

舉例如下-全局變數:

<script type="text/javascript">
    var c = 100;
    function fun(){
        var a = 100;
        console.log(c)
    }
    fun();
    console.log(c);
</script>

在全局定義一個全局變數c,不僅能在fun函數內部被訪問,在函數外依舊能被訪問。

2.間接訪問局部變數

<script type="text/javascript">
    function fun(){
        var a = 100;
        function fun1(){
            console.log(a);
        }
        fun1();
    }
    fun();
</script>

通過調用fun1把a列印出來,fun1是可以訪問fun的所有變數

3.作用域鏈

可以參考我的這篇文章JS之預編譯和執行順序(全局和函數)可以更好的理解預編譯的原理,為作用域鏈做準備。

舉例:

<script type="text/javascript">
    var a = 100;
    function fun(){
        var b = 200
        function fun2(){
            var c = 300
        }
        function fun3(){
            var d = 400
        }
        fun2()
        fun3()
    }
    fun()
</script>

首先預編譯,一開始生成一個GO{

  a:underfined

  fun:function fun(){//fun的函數體

      var b = 200
      function fun2(){
        var c = 300
      }
      function fun3(){
      var d = 400
      }
      fun2()
      fun3()
    }

}

逐行執行代碼,GO{

  a:100

  fun:function fun(){//fun的函數體

      var b = 200
      function fun2(){
        var c = 300
      }
      function fun3(){
      var d = 400
      }
      fun2()
      fun3()
    }

}

當fun函數執行時,首先預編譯會產生一個AO{

  b:underfined

  fun2:function fun2(){
       var c = 300
     }

  fun3:function fun3(){
      var d = 400
     }

}

這裡註意的是fun函數是在全局的環境下產生的,所以自己身上掛載這一個GO,由於作用域鏈是棧式結構,先產生的先進去,最後出來,

在這個例子的情況下,AO是後於GO產生的,所以對於fun函數本身來說,執行代碼的時候,會先去自己本身的AO里找找看,如果沒有找到要用的東西,就去父級查找,此題的父級是GO

此刻fun的作用域鏈是  第0位    fun的AO{}

          第1位    GO{}

fun函數開始逐行執行AO{

  b:200

  fun2:function fun2(){
       var c = 300
     }

  fun3:function fun3(){
      var d = 400
     }

 }

註意:函數每次調用才會產生AO,每次產生的AO還都是不一樣的

然後遇到fun2函數的執行,預編譯產生自己的AO{

  c:underfined

}

此刻fun2的作用域鏈是第0位    fun2的AO{}

          第1位    fun的AO{}

          第2位    GO{}

然後遇到fun3函數的執行,預編譯產生自己的AO{

  d:underfined

}

此刻fun3的作用域鏈是第0位    fun3的AO{}

          第1位    fun的AO{}

          第2位    GO{}

fun2和fun3的作用域鏈沒有什麼聯繫

當函數fun2和fun3執行完畢,自己將砍掉自己和自己的AO的聯繫,

最後就是fun函數執行完畢,它也是砍掉自己和自己AO的聯繫。

這就是一個我們平時看到不是閉包的函數。

4.閉包

1.閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。

2.寫法:

 1 <script type="text/javascript">
 2     function fun1(){
 3         var a = 100;
 4         function fun2(){
 5             a++;
 6             console.log(a);
 7         }
 8         return fun2;
 9     }
10     
11     var fun = fun1();
12     fun()
13     fun()
14 </script>

3.效果如下:

4.分析:

執行代碼

GO{

fun:underfined

fun1:function fun1()

   {

     var a = 100;

     function fun2()

    {

        a++;

        console.log(a);

     }

     return fun2;

     }

 

}

然後第十一行開始這裡,就是fun1函數執行,然後把fun1的return賦值給fun,這裡比較複雜,我們分開來看,

這裡fun1函數執行,產生AO{

a:100

fun2:function fun2(){

    a++;
    console.log(a);
    }

}

此刻fun1的作用域鏈為 第0位   AO

           第1位   GO

此刻fun2的作用域鏈為 第0位   fun1的AO

           第1位   GO

解釋一下,fun2只是聲明瞭,並沒有產生調用,所以沒有產生自己的AO,

正常的,我們到第7行代碼我們就結束了,但是這個時候來了一個return fun2,把fun2這個函數體拋給了全局變數fun,好了,fun1函數執行完畢,消除自己的AO,

此刻fun2的作用域鏈為 第0位   fun1的AO

           第1位   GO

第十二行就是fun執行,然後,它本身是沒有a的,但是它可以用fun1的AO,然後加,然後列印,

因為fun中的fun1的AO本來是應該在fun1銷毀時,去掉,但是被拋給fun,所以現在fun1的AO沒辦法銷毀,所以現在a變數相當於一個只能被fun訪問的全局變數。

所以第十三行再調用一次fun函數,a被列印的值為102.

5.閉包之深入理解

舉例1:

 1 <script type="text/javascript">
 2     function fun1(){
 3         var a = 100;
 4         function fun2(){
 5             a ++;
 6             console.log(a);
 7         }
 8         
 9         return fun2;
10     }
11     var fn1 = fun1();   //生成自己的AO,上面有a
12     var fn2 = fun1();
13     fn1()//101
14     fn1()//102
15     fn2()//101
16 </script>

fn1和fn2互不幹涉,因為fun1函數調用了兩次,所以兩次的AO是不一樣的。

舉例2:

 1 <script type="text/javascript">
 2 function fun(){
 3     var num = 0;
 4     function jia(){
 5         num++;
 6         console.log(num);
 7     }
 8     function jian(){
 9         num--;
10         console.log(num)
11     }
12     return [jia,jian];
13 }
14 var fn = fun();
15 var jia = fn[0];
16 var jian = fn[1];
17 jia()//1
18 jian()//0
19 </script>

jia和jian共用一個fun的AO,一動全都動,十二行返回了一個數組,

舉例3:

 1 <script type="text/javascript">
 2 function fun(){
 3     var num = 0;
 4     function jia(){
 5         num++;
 6         console.log(num);
 7     }
 8     function jian(){
 9         num--;
10         console.log(num)
11     }
12     return [jia,jian];
13 }
14 var jia = fun()[0];
15 var jian = fun()[1];
16 jia()//1
17 jian()//-1
18 </script>

 這裡有一個坑,jia = fun()[0]; jian = fun()[1];fun函數執行了兩遍,所以兩次的AO不一樣,所以jia和jian操作的對象不一樣。

6.閉包好處與壞處

好處:

①保護函數內的變數安全 ,實現封裝,防止變數流入其他環境發生命名衝突

②在記憶體中維持一個變數,可以做緩存(但使用多了同時也是一項缺點,消耗記憶體)

③匿名自執行函數可以減少記憶體消耗

壞處:

①其中一點上面已經有體現了,就是被引用的私有變數不能被銷毀,增大了記憶體消耗,造成記憶體泄漏,解決方法是可以在使用完變數後手動為它賦值為null;

②其次由於閉包涉及跨域訪問,所以會導致性能損失,我們可以通過把跨作用域變數存儲在局部變數中,然後直接訪問局部變數,來減輕對執行速度的影響

7.閉包解決的問題

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <ul>
            <li>0</li>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
            <li>7</li>
            <li>8</li>
            <li>9</li>
        </ul>
        <script type="text/javascript">
            var lis = document.getElementsByTagName("li");
            for(var i = 0;i < lis.length;i++){
                lis[i].onclick = function(){
                    console.log(i)
                }
                
            }
        </script>
    </body>
</html>

不管點擊哪個都是10,那是因為點擊事件是我們點擊才觸發的函數,等到觸發的時候,i早就變成10跳出迴圈了,,這個時候我們就需要立即執行函數,創造了十個不同的作用域

解決方案:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <ul>
            <li>0</li>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
            <li>7</li>
            <li>8</li>
            <li>9</li>
        </ul>
        <script type="text/javascript">
            var lis = document.getElementsByTagName("li");
            for(var i = 0;i < lis.length;i++){
//                lis[i].onclick = function(){
//                    console.log(i)
//                }
                (function(i){
                    lis[i].onclick = function(){
                        console.log(i)
                    }
                })(i)
            }
        </script>
    </body>
</html>

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 需要在最後重置一下輪播 ...
  • 先看下這段代碼: 解析: 1、JSX全稱JavaScript XML,在js中寫XML標簽,是javascript的一種擴展語法。JSX標簽語法既不是字元串也不是 HTML,在編譯之後,JSX 其實會被轉化為普通的 JavaScript 對象,描述要顯示的UI信息。 JSX的編譯過程如圖所示: 2、 ...
  • 所有基礎課程鏈接: 1.JavaScript基礎視頻教程總結(001-010章) 2.JavaScript基礎視頻教程總結(011-020章) 3. JavaScript基礎視頻教程總結(021-030章) 4. JavaScript基礎視頻教程總結(031-040章) 5. JavaScript基 ...
  • 1.什麼是作用域(scope)? 簡單來講,作用域(scope)就是變數訪問規則的有效範圍。 作用域外,無法引用作用域內的變數; 離開作用域後,作用域的變數的記憶體空間會被清除,比如執行完函數或者關閉瀏覽器 作用域與執行上下文是完全不同的兩個概念。我曾經也混淆過他們,但是一定要仔細區分。 JavaSc ...
  • 今天在寫一個小的 CSS Demo,一個關於 3d 球的旋轉動畫,關於 CSS 3D,少不了會使用下麵這幾個屬性: 這個 Demo 你可以戳這裡,大概是這樣:CodePen Demo - 3D ball: 嗯,大概到了這個效果,想到了 CSS 混合模式 mix-blend-mode,尋思著,利用混合 ...
  • 使用create react app腳手架搭建環境 1、安裝node 。軟體下載地址:,我下的推薦的版本。 安裝之後測試是否安裝成功。windows系統下,輸入Win+R輸入cmd ,打開運行視窗,輸入 node v 顯示版本號則表示安裝成功。接下來可以看下是否安裝npm,輸入npm v ,顯示版本 ...
  • 首先給一個神奇的圖: 我的反應,精分吧!一會兒true一會兒false的。。。 後來發現,把g去掉後就正常了,那這是為什麼呢??lastIndex惹得鬼! 正文: lastIndex 全局正則表達是,有一個屬性:lastIndex,這個屬性是用來存放上一次匹配文本之後的第一個字元的位置。 exec( ...
  • 通過 jQuery,可以很容易地添加新元素/內容。 添加新的 HTML 內容 一共有四種方法: append() - 在被選元素的結尾插入內容 prepend() - 在被選元素的開頭插入內容 after() - 在被選元素之後插入內容 before() - 在被選元素之前插入內容 以下為jQuer ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...