這篇文章為大家帶來了關於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 變量,這就是閉包函數能存取外部函數變數。
看到Closure 中fn 是閉包函數,其中保存num 變數。
for (var i = 1; i { console.log(i); }, i * 1000); }
輸出的結果都是 6,為什麼?
for 循環一次,就會將setTimeout 非同步任務加入瀏覽器的非同步任務佇列中,同步任務完成之後,再從非同步任務中拿新任務在執行緒中執行。由於 setTimeout 能夠存取外部變數 i, 當同步任務完成之後,i 已經變成了6, setTimeout 中能夠存取變數 i 都是 6。
for (var i = 1; i { console.log(i); }, i * 1000); }
for (var i = 1; i { console.log(i); }, i * 1000) })(i) }
#第三個參數意思:附加參數,一旦計時器到期,它們會作為參數傳遞給要執行的函數
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); } }
問題說明:父/子
元件關係, 父子元件都能使用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
問題是點擊子元件時候,輸出的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 中,使用時要注意閉包陷阱。
- #開啟開發者工具,選擇Timeline 面板
- 在頂部的
Capture
欄位裡面勾選Memory- 點選左上角的錄製按鈕。
- 在頁面上進行各種操作,模擬使用者的使用情況。
- 一段時間後,點擊對話框的 stop 按鈕,面板上就會顯示這段時間的記憶體佔用情況。
【相關推薦:JavaScript影片教學、web前端】
#以上是簡單了解JavaScript閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!