夯實基礎中篇-圖解作用域鏈和閉包

来源:https://www.cnblogs.com/qcjay/archive/2022/04/18/16159562.html
-Advertisement-
Play Games

講基礎不容易,本文通過 7個demo、6張圖、1.6k文字串講作用域鏈、詞法作用域、閉包、閉包使用案例。 ...


前言

本文承接上篇 夯實基礎上篇-圖解 JavaScript 執行機制,請先閱讀上篇~

講基礎不容易,本文通過 7個demo和6張圖,和大家一起學習溫故作用域鏈和閉包,本文大綱:

  1. 什麼是作用域鏈
  2. 什麼是詞法作用域
  3. 什麼是閉包
  4. 閉包的實際使用案例

什麼是作用域鏈

正文開始~

請思考下麵 demo 的 name 列印什麼

    function test() {
      console.log(name)
    }
    function test1() {
      const name = 'test1的name'
      test()
    }
    const name = 'global的name'
    test1()

通過執行上下文來分析代碼的執行流程,執行到 test 函數時:

image.png

那 test 函數里的 name 是哪個呢?這就涉及到了作用域鏈的定義:變數和函數的查找鏈條就是作用域鏈。它決定了各級上下文中的代碼在訪問變數和函數時的順序:查找變數和函數時,先在當前執行上下文找,當前沒有,到下一個執行上下文找,沒有再到下一個,直到全局執行上下文,都沒有就報錯使用未定義的變數或函數

而在每個執行上下文的變數環境中,都包含了一個外部引用 outer,用來指向外部的執行上下文,鏈條結構是當前執行上下文 > 包含當前上下文的上下文1 > 包含上下文1的上下文2 ...

而這個 demo 會列印global的name,原因是 test 執行上下文的 outer 指向全局執行上下文,包括 test1 的 outer 也是指向全局執行上下文:

image.png

也許會有同學疑惑,為什麼 test 的 outer 指向全局執行上下文,而不是 test1,這是因為在 JavaScript 執行過程中,其作用域鏈是由詞法作用域決定的。

什麼是詞法作用域

詞法作用域就是作用域是由代碼中函數聲明的位置來決定的,它是靜態的作用域,通過它就能夠預測代碼在執行過程中如何查找標識符,它與函數是怎樣調用的沒有關係。所以剛纔的例子列印的是global的name

看個具體例子:

    const count = 0
    function test() {
      const count = 1

      function test1() {
        const count = 2

        function test2() {
          const count = 3
        }
      }
    }

其包含關係和作用域鏈:

image.png

image.png

事實上在 Global Scope 全局作用域(Window)之前,還有一個 Script Scope 腳本作用域,它存放的是當前 Script 內可訪問的 let 變數和 const 變數,而 var 變數存放在 Global 上的就不在 Script Scope,它類似於是腳本範圍內的全局作用域。在下麵的 demo 中再舉例。

什麼是閉包

閉包指的是那些引用了另一個函數作用域中變數的函數,通常是在嵌套函數中實現的。

比如這個例子:

    var globalVariable = 1
    const scriptVariable = 2
    
    function test() {
      let name = 'Jaychou'

      return {
        getName() {
          const count = 1
          return name
        },
        setName(newValue) {
          name = newValue
        }
      }
    }

    const testFun = test()
    console.log(testFun.getName()) // Jaychou
    testFun.setName('小明')
    console.log(testFun.getName()) // 小明

大家可以根據作用域鏈的知識,思考一下執行到console.log(testFun.getName())的 getName 裡面的時候作用域鏈是怎樣的~

我們用瀏覽器的開發者工具看一下:

image.png
作用域鏈是當前作用域 》test 函數的閉包 》Script 作用域 》Global 作用域

  1. 為什麼叫 test 函數的閉包?因為當const testFun = test()的 test 函數執行完之後,test 的函數執行上下文已經被銷毀了,但它返回的{ getName(){}, setName(){} }對象被 testFun 引用著,而 getName 和 setName 引用著 test 函數內定義的 name 變數,所以這些被引用的變數依然需要被保存在記憶體中,而這些變數的集合稱為閉包 Closure;
  2. 目前閉包內的 name 變數就只能通過 getName 和 setName 去訪問和設置,而這也是閉包的作用之一:封裝私有變數;
  3. 剛纔說的 Script Scope 中保存著 scriptVariable 變數,globalVariable 變數是 var 聲明的,所以在 Global Scope(Window)中。

再看1個具體案例理解閉包:

    const globalCount = 0

    function test() {
      const count = 0
      return test1

      function test1() {
        const count1 = 1
        return test2

        function test2() {
          const count2 = 2
          console.log('test2', globalCount + count + count1 + count2)
        }
      }
    }
    test()()()

image.png
執行到 test2 內部的 console.log 那一行時,其作用域鏈是當前作用域 》test1 的閉包 》test 的閉包 》Script Scope 》Global Scope

閉包使用建議:當不需要使用了之後,註意要解除引用著閉包的變數,這樣閉包才會被釋放。比如第1個案例的 testFun 如果不需要用了,就把它釋放 testFun = null。

閉包的實際使用案例

封裝私有變數

就是剛纔的 getName、setName 案例,通過 getName 獲取 name,通過 setName 設置 name

封裝單例

    const Single = (function () {
      let instance = null

      return function () {
        if (!instance) {
          instance = {
            name: 'jaychou',
            age: 40
          }
        }
        return instance
      }
    })()
    const obj1 = new Single()
    const obj2 = new Single()
    console.log(obj1 === obj2) // true

這裡只是舉個例子,具體的 instance 是什麼類型,支持什麼功能要看實際項目。

防抖和節流

防抖:

    function debounce(fn, delay) {
      let timer = null;

      return function () {
        let context = this;
        let args = arguments;

        timer && clearTimeout(timer);

        timer = setTimeout(function () {
          fn.apply(context, args);
        }, delay);
      }
    }

節流:

    function throttle(fn, interval) {
      let last = 0;

      return function () {
        let now = +new Date()
        if (now - last >= interval) {
          fn.apply(this, arguments);
          last = now;
        }
      }
    }

更完整的防抖和節流的實現可參考 Lodash ,這裡主要是演示閉包的使用

總結

閉包的使用場景很多,功能很強大,可以說在前端項目中經常可見例如 React Hooks 等等,這裡只列舉了幾個很簡單的很實用的應用場景。

總結

本文主要介紹了作用域鏈和閉包,沿著 夯實基礎上篇-圖解 JavaScript 執行機制 來一起看的話應該比較容易理解,若對大家有所幫助,請不吝點贊關註~


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

-Advertisement-
Play Games
更多相關文章
  • 安裝軟體 pacman -S (軟體名):安裝軟體,若有多個軟體包,空格分隔 pacman -S --needed (軟體名):安裝軟體,若存在,不重新安裝最新的軟體 pacman -Sy (軟體名):安裝軟體前,先從遠程倉庫下載軟體包資料庫 pacman -Sv (軟體名):輸出操作信息後安裝 p ...
  • MySQL 的 GRANT和REVOKE 命令 GRANT - 授權 將指定 操作對象 的指定 操作許可權 授予指定的 用戶; 發出該 GRANT語句的可以是資料庫管理員,也可以是該資料庫對象的創建者; 查詢 查看用戶自己許可權 SHOW GRANTS; 查看其他用戶許可權 SHOW GRANTS FOR ...
  • 我們設計了一款分散式菜單應用,不需要個人去關註公眾號或下載小程式,服務員會提供幾個點單的平板,連接店鋪網路,區域網內通信,這樣大家點單、查看訂單詳情等都不受網路限制。 ...
  • 核酸檢測結果包含個人隱私數據且數據量較大,如何在雲端讓核酸檢測人員實現海量數據的安全存儲/查詢成為了核酸檢測數據存儲的首要難題。華為AppGallery Connect提供了認證服務和雲資料庫服務兩大Serverless服務,可分別實現用戶認證登錄、數據寫入/查詢等基本端雲協同功能,可完美解決核酸檢 ...
  • 近日,華為HMS Core手語服務攜手吉林大學、長春大學特教學院聯合打造暖心課堂,在直播網課中加入AI手語翻譯,於人文中融入科技,知識中融入溫暖。 手語翻譯:同學們大家好 HMS Core手語服務通過AI賦能教育,儘可能地為聽障人群提供更多優質的教學資源,讓他們能夠享受到更加公平、更高質量的公共教育 ...
  • 近期在做 epub.js 引擎解析電子書小項目,在閱讀界面通過電子書 rendition 對象的 touch 事件進行手勢翻頁功能時(圖1), 圖1 滑動頁面出現上下和左右方向上的空白部分以及會有回彈的效果(圖2), 圖2 剛開始感覺還挺好看的但後面越感覺越不對,這樣的用戶體驗個人感覺還不如固定視窗 ...
  • 作者:安小軒 原文鏈接:https://juejin.cn/post/7086272341994536974 實現一個旋轉的立方體,只需要用css的基本屬性就可以實現。我們一起看看吧~ 一:transform 基本屬性 transform可以實現元素的2D或3D轉換,可以對元素進行旋轉,縮放,移動, ...
  • 一、起因 最近在使用Umi進行React的前端開發,有一個數據表格分頁的功能需求,由於後端還沒完成所以考慮前端先使用Mock先來進行模擬數據測試。 Mock的介紹這裡就不做贅述,大家感興趣的可自行前往官網學習--Mock.js。 由於是分頁功能,必然少不了當前頁碼、分頁大小、過濾條件等請求參數,但是 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...