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

簡單了解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號
久久精品视频国产| 国产精品成人A区在线观看 | 一区二区日韩国产精品| 久久精品国产亚洲av麻| 国产精品无码素人福利| 亚洲欧美精品午睡沙发| 亚洲精品无码鲁网中文电影| 国产麻豆剧传媒精品网站| 久久久国产成人精品| 丁香六月婷婷精品免费观看| 九九精品视频在线观看| 99视频在线观看精品| 国产精品爆乳奶水无码视频| 无码精品A∨在线观看中文| 日韩大乳视频中文字幕| 国产成人综合久久精品红| 中文精品久久久久国产网址| 精品国产香蕉伊思人在线在线亚洲一区二区| 精品国产乱码久久久久久| 久久国产精品2020免费m3u8| 777午夜精品久久av蜜臀| 久久精品无码免费不卡| 久久精品无码一区二区日韩AV| 在线观看精品国产福利片100| 精品无码成人久久久久久| 91精品视频在线免费观看| 亚洲日本精品一区二区| 午夜精品久久久久久99热| 国产产无码乱码精品久久鸭| 国内精品久久久久影院日本| 2015日韩永久免费视频播放| 国产69精品久久久久APP下载| 日韩精品免费一级视频| 国产精品无码久久久久久| 久久夜色精品国产www| 精品久久久久久久久久中文字幕 | 国产69精品久久久久观看软件 | 午夜激情经典日韩| 亚洲AV无码专区日韩| 日韩精品人妻系列无码专区| 亚洲日韩区在线电影|