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

        簡單了解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號
        主站蜘蛛池模板: 亚洲欧美日韩久久精品| 国产精品亚洲日韩欧美色窝窝色欲| 在线精品亚洲一区二区小说| 久久福利青草精品资源站免费| 亚洲精品无码午夜福利中文字幕 | 无翼乌无遮挡全彩老师挤奶爱爱帝国综合社区精品 | 久久精品国产福利国产琪琪| 国产精品久久久久久久| 无码国产精品一区二区免费模式| 日韩精品中文字幕无码一区| 国产成人精品综合久久久| 国产精品美脚玉足脚交欧美| 中文精品人人永久免费| 蜜臀精品国产高清在线观看| 国产精品一区二区久久精品无码| 欧美精品1区2区| 91精品在线看| 国产麻豆精品一区二区三区v视界| 中日韩产精品1卡二卡三卡| 欧美激情视频精品一区二区| 精品成人av一区二区三区| 国产高清一级毛片精品| 一级香蕉精品视频在线播放| 久久91精品国产91久久麻豆| 国产成人精品高清在线观看99| 99久久国产热无码精品免费| 无码人妻精品一区二区三区66| 亚洲欧洲国产精品香蕉网| 亚洲精品国产电影| 亚洲国产精品成人| 亚洲国产一成久久精品国产成人综合| 久久精品国产亚洲7777| 久久精品国产一区二区三区不卡| 久久久久国产精品麻豆AR影院 | 在线涩涩免费观看国产精品| 无码人妻精品一区二| 中文字幕久精品免费视频| 亚洲精品无码av人在线观看| 亚洲国产精品无码久久一区二区 | 欧美韩国精品另类综合| 亚洲午夜精品一区二区|