首頁  >  文章  >  web前端  >  簡單了解JavaScript閉包

簡單了解JavaScript閉包

WBOY
WBOY轉載
2023-01-21 06:30:021099瀏覽

這篇文章為大家帶來了關於JavaScript的相關知識,其中主要介紹了關於JavaScript閉包的相關問題,閉包的概念有很多版本,不同的地方對閉包的說法不一,下面一起來看一下,希望對大家有幫助。

簡單了解JavaScript閉包

什麼是閉包?

閉包的概念是有很多版本,不同的地方對閉包的說法不一

維基百科:在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是在支援頭等函數的程式語言中實現詞法綁定的一種技術。

MDN: 閉包(closure)是一個函數以及其捆綁的周邊環境狀態(lexical environment詞法環境)的引用的組合。

個人理解:

  • 閉包是一個函數(傳回一個函數)
  • 傳回的函數保存了對外變數參考

一個簡單的範例

function fn() {    let num = 1;    return function (n) {        return n + num
    }
}let rFn = fn()let newN = rFn(3) // 4

num 變數作用域在fn 函數中, rFn 函數卻能存取num 變量,這就是閉包函數能存取外部函數變數。

從瀏覽器調試和VSCode Nodejs 調試看閉包

  • 瀏覽器

簡單了解JavaScript閉包

  • VS Code 配合Node.js

簡單了解JavaScript閉包

看到Closure 中fn 是閉包函數,其中保存num 變數。

一個經典的閉包:單執行緒事件機制 迴圈問題,以及解決方法

for (var i = 1; i  {    console.log(i);
  }, i * 1000);
}

輸出的結果都是 6,為什麼?

  • for 迴圈是同步任務
  • setTimeout 非同步任務

for 循環一次,就會將setTimeout 非同步任務加入瀏覽器的非同步任務佇列中,同步任務完成之後,再從非同步任務中拿新任務在執行緒中執行。由於 setTimeout 能夠存取外部變數 i, 當同步任務完成之後,i 已經變成了6, setTimeout 中能夠存取變數 i 都是 6。

解決方法1:使用let 宣告

for (var i = 1; i  {    console.log(i);
  }, i * 1000);
}

解決方法2:自執行函數閉包

for (var i = 1; i  {    console.log(i);
  }, i * 1000)
  })(i)
}

解決方法3:setTimeout 傳遞第三個參數

#第三個參數意思:附加參數,一旦計時器到期,它們會作為參數傳遞給要執行的函數

for (var i = 1; i  {    console.log(j);
  }, 1000 * i, i);
}

閉包與函數科里化

function add(num) {  return function (y) {    return num + y;
  };
};let incOneFn = add(1); let n = incOneFn(1);  // 2let decOneFn = add(-1); let m = decOneFn(1); // 0

add 函數的參數保存了閉包函數變數。

實際作用

在函數式程式設計閉包有非常重要的作用,lodash 等早期工具函數彌補 javascript 缺陷的工具函數,有大量的閉包的使用場景。

使用場景

  • 建立私有變數
  • #延長變數生命週期

節流函數

防止捲動行為,過度執行函數,必須要節流, 節流函數接受函數 時間作為參數,都是閉包中變數,以下是一個簡單setTimeout 版本:

function throttle(fn, time=300){    var t = null;    return function(){        if(t) return;
        t = setTimeout(() => {
            fn.call(this);
            t = null;
        }, time);
    }
}

防抖函數

一個簡單的基於setTimeout 防手震的函數的實作

function debounce(fn,wait){    var timer = null;    return function(){        if(timer !== null){            clearTimeout(timer);
        }
        timer = setTimeout(fn,wait);
    }
}

React. useCallback 閉包陷阱問題

問題說明:父/子 元件關係, 父子元件都能使用click 事件同時修改state 資料, 且子元件拿到傳遞下的props 事件屬性,是經過useCallback 優化過的。也就是這個被優化過的函數,存在閉包陷阱,(保存一直是初始state 值)

import { useState, useCallback, memo } from "react";const ChildWithMemo = memo((props: any) => {  return (    <div>
      <button>Child click</button>
    </div>
  );
});const Parent = () => {  const [count, setCount] = useState(1);  const handleClickWithUseCallback = useCallback(() => {    console.log(count);
  }, []); // 注意这里是不能监听 count, 因为每次变化都会重新绑定,造成造成子组件重新渲染

  return (    <div>
      <div>parent count : {count}</div>
      <button> setCount(count + 1)}>click</button>
      <childwithmemo></childwithmemo>
    </div>
  );
};export default Parent
  • ChildWithMemo 使用memo 進行最佳化,
  • handleClickWithUseCallback 使用useCallback 最佳化

問題是點擊子元件時候,輸出的count 是初始值(被閉包了)。

解決方法就是使用useRef 來保存操作變數函數:

import { useState, useCallback, memo, useRef } from "react";const ChildWithMemo = memo((props: any) => {  console.log("rendered children")  return (    <div>
      <button> props.countRef.current()}>Child click</button>
    </div>
  );
});const Parent = () => {  const [count, setCount] = useState(1);  const countRef = useRef<any>(null)

  countRef.current = () => {    console.log(count);
  }  return (    <div>
      <div>parent count : {count}</div>
      <button> setCount(count + 1)}>click</button>
      <childwithmemo></childwithmemo>
    </div>
  );
};export default Parent</any>

針對這個問題,React 曾經認可過社群提出的增加useEvent 方案,但是後面useEvent 語意問題被放棄了,對於渲染最佳化React 採用了編譯最佳化的方案。其實類似的問題也會發生在 useEffect 中,使用時要注意閉包陷阱。

效能問題

  • 閉包不要隨意定義,定義了一定找到合適的位置進行銷毀。因為閉包的變數保存在記憶體中,不會被銷毀,佔用較高的記憶體。

使用chrome 面板功能timeline profiles 面板

  1. #開啟開發者工具,選擇Timeline 面板
  2. 在頂部的Capture欄位裡面勾選Memory
  3. 點選左上角的錄製按鈕。
  4. 在頁面上進行各種操作,模擬使用者的使用情況。
  5. 一段時間後,點擊對話框的 stop 按鈕,面板上就會顯示這段時間的記憶體佔用情況。

【相關推薦:JavaScript影片教學web前端

#

以上是簡單了解JavaScript閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除