亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript閉包是什么及怎么用

發布時間:2022-11-08 09:59:27 來源:億速云 閱讀:101 作者:iii 欄目:web開發

這篇“JavaScript閉包是什么及怎么用”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“JavaScript閉包是什么及怎么用”文章吧。

閉包是什么?

對于一個知識點來說,我一直認為不論是從什么方面入手,都需要徹底弄懂三個問題,才算真正了解這個知識點,然后具體再去實踐中練習,才能稱得上掌握。這三個問題就是:

  • 是什么?

  • 為什么要設計?

  • 能用在哪?

首先先回答閉包是什么這個問題。應該大多數人也看過很多與之相關的文章,很多人也給出了自己的解釋,所以我也先給出自己理解的解釋,那就是: 先有兩個前置的概念:

  • 閉包是在詞法分析時就已經被確定的, 所以它會與詞法作用域有關。

  • 閉包存在的前置條件是需要支持函數作為一等公民的編程語言,所以它會與函數有關。

所以最終的結論就是:

  • 閉包首先是一個結構體,這個結構體的組成部分為  一個函數 + 該函數所處的詞法作用域

  • 也就是閉包是由一個函數并且該函數能夠記住聲明自己的詞法作用域所產生的結構體

  • 在內存中理解就是, 當一個函數被調用時,它所產生的函數執行上下文里的作用域鏈保存有其父詞法作用域,所以父變量對象由于存在被引用而不會銷毀,駐留在內存中供其使用。這樣的情況就稱為閉包。

為什么要設計出閉包?

對于為什么設計這點,僅以我自己粗淺的理解就是由于JavaScript是異步單線程的語言。對于異步編程來說,最大的問題就是當你編寫了函數,而等到它真正調用的時機可能是之后任意的時間節點。

這對于內存管理來說是一個很大的問題,正常同步執行的代碼,函數聲明時和被調用時所需要的數據都還存留在內存中,可以無障礙的獲取。而異步的代碼,往往聲明該函數的上下文可能已經銷毀,等到在調用它時,如果內存中已經把它所需要的一些外部數據給清理了,這就是個很大的問題。

所以JavaScript解決的方案就是讓函數能夠記得自己之前所能獲取數據的范圍,統統都保存在內存里,只要該函數沒有被內存回收,它自身以及所能記住的范圍都不會被銷毀

這里的所能記住的范圍就是指詞法作用域,就是由于它是靜態的,所以才需要記住。

這又是JavaScript設計作用域為靜態的導致的。 如果是動態作用域,函數被調用時只需要被調用時的那個環境,就不需要存在記住自身作用域的事了。

所以總結一下就是:

  • 閉包是為了解決詞法作用域引發的問題內存不好管理異步編程里數據獲取所產生的。

經典題

由于有非常多的文章都從下面這個非常經典的面試題入手,但似乎都沒有人真正從最底層講解過,所以我就打算將整個過程梳理一遍,來明白這其中的差異性。

for (var i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}

基本所有有基礎的人一眼就能看出輸出的是三個3。

然后讓修改成按順序輸出,通常只需要修改var成let:

for (let i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}

這樣就成了輸出為0,1,2.并且是同時間輸出,而不是每間隔一秒輸出一次。

那么問題來了,為什么?

這里可以先不看下面,先寫寫自己的解釋,看看是否跟我寫的一樣。

1. 先來探討變量i是var的情況。

當代碼開始執行時,此時執行上下文棧和內存里的情況是這樣: 其中全局對象里的變量i和全局執行上下文里變量環境里的變量i是同一個變量。

JavaScript閉包是什么及怎么用

然后開始進行循環, 當 i = 0時,第一個定時器被丟入宏任務隊列,關于宏任務相關的內容屬于事件循環范疇,暫時只需要理解setTimeout會被丟入隊列里,等之后執行。 此時在堆內存中會創建它的回調函數cb,并且函數創建時會創建[[scope]],在實際ECMA的規則中,[[scope]]會指向該函數的父作用域,也就是當前的全局對象(作用域是概念上的東西,實際體現在內存中就是保存數據的一種結構,可能是對象也可能是其他)。 但是在V8引擎的實現中,其實并不會指向全局對象,而是去分析該函數使用了父作用域中的哪些變量,將這些變量存儲到Closure中,然后由scope指向。每個函數都有且只有一個Closure對象。

JavaScript閉包是什么及怎么用


這里先插入一下關于Closure對象可以在Chrome中哪看到的情況: 可以看到,創建bar函數時,它只有引用了父作用域的name變量,所以在閉包對象中只會存儲變量name, 而不會存在變量age。JavaScript閉包是什么及怎么用


同理之后的 i = 1, 和 i = 2 都是一樣的,最終結果會變成:

JavaScript閉包是什么及怎么用

最終因為 i++導致 i = 3, 循環結束,全局代碼執行完畢。此時的結果為:

然后開始進入定時器回調函數執行的過程, 開始執行第一個定時器里的回調函數,壓入了執行上下文棧中,執行輸出i, 但是在詞法環境和變量環境中找不到這個變量i,所以去自身[[scope]]向上尋找,在Closure對象中找到了 i 等于3,輸出結果3。

JavaScript閉包是什么及怎么用

同理對于后面兩個定時器也是一樣的流程,并且實際上定時器開啟的時間都是在循環中就立即執行的,導致實際上三個函數的定時1秒時間是一致的,最終輸出的結果是幾乎同時輸出3個3。而不是每間隔1秒后輸出3, 當然這是定時器相關的知識了。

2. 然后探討通過var修改成let之后實際上變了什么

同樣是剛創建時,所展示的情況為:

JavaScript閉包是什么及怎么用

之后進入循環體,當i = 0時:

JavaScript閉包是什么及怎么用

之后進入 i = 1時的情況:

JavaScript閉包是什么及怎么用

最后進入到 i = 2的情況,與 i = 1基本類似:

JavaScript閉包是什么及怎么用

最終 i++,變成i值為3,循環結束。開啟定時器工作:

JavaScript閉包是什么及怎么用

當執行第一個定時器的回調函數時,創建了函數執行上下文,此時執行輸出語句i時,會先從自己的詞法環境里尋找變量i的值,也就是在 record環境記錄里搜索,但是不存在。因而通過自己外部環境引用outer找到原先創建的塊級作用域里 i = 0的情況, 輸出了i值為0的結果。

對于之后的定時器也都是一樣的情況,原先的塊級作用域由于被回調函數所引用到了,因而就產生了閉包的情況,不會在內存中被銷毀,而是一直留著。

等到它們都執行完畢后,最終內存回收會將之全部都銷毀。

其實以上畫的圖并不是很嚴謹,與實際在內存中的表現肯定是有差異的,但是對于理解閉包在內存里的情況還是不影響的。

閉包能用在哪?

首先需要先明確一點,那就是在JavaScript中,只要創建了函數,其實就產生了閉包。這是廣義上的閉包,因為在全局作用域下聲明的函數,也會記著全局作用域。而不是只有在函數內部聲明的函數才叫做閉包。

通常意義上所討論的閉包,是使用了閉包的特性

1. 函數作為返回值

let a = 1function outer() {  let a = 2

  function inside() {
    a += 1
    console.log(a)
  }  return inside
}const foo = outer()foo()

此處outer函數調用完時,返回了一個inside函數,在執行上下文棧中表示的既是outer函數執行上下文被銷毀,但有一個返回值是一個函數。 該函數在內存中創建了一個空間,其[[scope]]指向著outer函數的作用域。因而outer函數的環境不會被銷毀。

當foo函數開始調用時,調用的就是inside函數,所以它在執行時,先詢問自身作用域是否存在變量a, 不存在則向上詢問自己的父作用域outer,存在變量a且值為2,最終輸出3。

2. 函數作為參數

var name = 'xavier'function foo() {  var name = 'parker'
  function bar() {    console.log(name)
  } console.log(name)  return bar
}function baz(fn) {  var name = 'coin'
  fn()
}baz(foo())baz(foo)

對于第一個baz函數調用,輸出的結果為兩個'parker'。 對于第二個baz函數的調用,輸出為一個'parker'。

具體的理解其實跟上面一致,只要函數被其他函數調用,都會存在閉包。

3. 私有屬性

閉包可以實現對于一些屬性的隱藏,外部只能獲取到屬性,但是無法對屬性進行操作。

function foo(name) {  let _name = name  return {    get: function() {      return _name
    }
  }
}let obj = foo('xavier')
obj.get()

4. 高階函數,科里化,節流防抖等

對于一些需要存在狀態的函數,都是使用到了閉包的特性。

// 節流function throttle(fn, timeout) {  let timer = null
  return function (...arg) {    if(timer) return
    timer = setTimeout(() => {
    fn.apply(this, arg)
    timer = null
    }, timeout)
  }
}// 防抖function debounce(fn, timeout){  let timer = null
  return function(...arg){    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arg)
    }, timeout)
  }
}

5. 模塊化

在沒有模塊之前,對于不同地方聲明的變量,可能會產生沖突。而閉包能夠創造出一個封閉的私有空間,為模塊化提供了可能性。 可以使用IIFE+閉包實現模塊。

var moduleA = (function (global, doc) {  var methodA = function() {};  var dataA = {};  return {    methodA: methodA,    dataA: dataA
  };
})(this, document);

以上就是關于“JavaScript閉包是什么及怎么用”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

蓝田县| 密云县| 五大连池市| 奉新县| 香港| 沅陵县| 吉木萨尔县| 府谷县| 佛学| 买车| 苏州市| 梓潼县| 北海市| 克拉玛依市| 彰化县| 甘谷县| 剑阁县| 苏尼特左旗| 绩溪县| 唐河县| 清苑县| 威信县| 民权县| 平利县| 丰城市| 湖北省| 墨竹工卡县| 安图县| 潢川县| 巴塘县| 大邑县| 广安市| 堆龙德庆县| 固安县| 汝南县| 抚州市| 临桂县| 辉县市| 嘉义市| 水城县| 邹城市|