站長資訊網
最全最豐富的資訊網站

簡單了解JavaScript閉包

本篇文章給大家帶來了關于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 <= 5; i++) {  setTimeout(() => {    console.log(i);   }, i * 1000); }
登錄后復制

登錄后復制

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

  • for 循環是同步任務
  • setTimeout 異步任務

for 循環一次,就會將 setTimeout 異步任務加入到瀏覽器的異步任務隊列中,同步任務完成之后,再從異步任務中拿新任務在線程中執行。由于 setTimeout 能夠訪問外部變量 i, 當同步任務完成之后,i 已經變成了6, setTimeout 中能夠訪問變量 i 都是 6。

解決辦法1:使用 let 聲明

for (var i = 1; i <= 5; i++) {  setTimeout(() => {    console.log(i);   }, i * 1000); }
登錄后復制

登錄后復制

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

for (var i = 1; i <= 5; i++) {   (function(i){      setTimeout(() => {    console.log(i);   }, i * 1000)   })(i) }
登錄后復制

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

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

for (var i = 1; i <= 5; i++) {  setTimeout((j) => {    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 onClick={props.handleClick}>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 onClick={() => setCount(count + 1)}>click</button>       <ChildWithMemo handleClick={handleClickWithUseCallback} />     </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 onClick={() => 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 onClick={() => setCount(count + 1)}>click</button>       <ChildWithMemo countRef={countRef} />     </div>   ); };export default Parent
登錄后復制

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

性能問題

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

使用 chrome 面板功能 timeline + profiles 面板

  1. 打開開發者工具,選擇 Timeline 面板
  2. 在頂部的Capture字段里面勾選 Memory
  3. 點擊左上角的錄制按鈕。
  4. 在頁面上進行各種操作,模擬用戶的使用情況。
  5. 一段時間后,點擊對話框的 stop 按鈕,面板上就會顯示這段時間的內存占用情況。

贊(0)
分享到: 更多 (0)
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
午夜人屠h精品全集| 日产精品一线二线三线芒果| 久久久无码精品亚洲日韩蜜臀浪潮| 国产99久久久国产精品小说| 182tv午夜精品视频在线播放| 久久精品国产大片免费观看| 三级精品在线观看| 国产精品久久久天天影视香蕉| 国产日韩美国成人| 四虎成人精品在永久在线| 国产精品高清一区二区人妖| 久久久国产精品福利免费| 国产亚州精品女人久久久久久| 亚洲国产精品专区在线观看| 日韩在线高清视频| 亚洲欧洲日韩综合| 亚洲 日韩经典 中文字幕| 日韩精品内射视频免费观看| 国产精品va一区二区三区| 国产高清在线精品二区| 精品国产自在现线久久| 国产愉拍精品手机| 东京热一精品无码AV| 精品国产日韩亚洲一区91| 夜夜精品视频一区二区| 国产精品乱码一区二区三| 国产精品无码无卡在线观看久| 国产乱码精品一区二区三区香蕉| 精品av天堂毛片久久久| 精品国产一区二区三区香蕉事| 日本精品一二三区| 国产suv精品一区二区6| 成人网站免费大全日韩国产| 午夜亚洲AV日韩AV无码大全| 亚洲欧美日韩中文高清www777| 日韩毛片免费在线观看| 无码国模国产在线无码精品国产自在久国产 | 91精品导航在线网址免费| 91精品国产色综合久久不| 亚洲国产成人91精品| 8090成人午夜精品|