JavaScript閉包實(shí)現(xiàn)函數(shù)返回函數(shù)詳解
前言
在JavaScript的世界里,閉包是一個(gè)既神秘又強(qiáng)大的特性。它既是JavaScript的一大難點(diǎn),也是JavaScript的特色之一。閉包的運(yùn)用貫穿于許多高級(jí)應(yīng)用中,可以說(shuō),掌握閉包,就能解鎖JavaScript編程的更多可能性。
一、閉包與變量作用域
要理解閉包,我們首先得從JavaScript的變量作用域講起。變量的作用域主要分為兩種:全局變量和局部變量。全局變量可以在代碼的任何地方被訪問(wèn),而局部變量則只能在其所在的函數(shù)內(nèi)部被訪問(wèn)。JavaScript語(yǔ)言的特別之處在于,函數(shù)內(nèi)部可以直接讀取全局變量,但函數(shù)外部卻無(wú)法讀取函數(shù)內(nèi)部的局部變量。閉包就可以實(shí)現(xiàn)。
二、閉包的實(shí)現(xiàn)-函數(shù)返回函數(shù)
閉包通常是通過(guò)函數(shù)返回函數(shù)的方式來(lái)實(shí)現(xiàn)的。這種模式在JavaScript中非常常見,它不僅增強(qiáng)了代碼的靈活性,還為閉包的實(shí)現(xiàn)提供了便利。以下是一個(gè)簡(jiǎn)單的例子,幫助你更好地理解閉包的實(shí)現(xiàn)方式:
// 定義一個(gè)函數(shù)用來(lái)打招呼 function greet(name) { return function () { console.log(`Hello, ${name}!`); }; } // 定義一個(gè)變量 接收 返回值 // 這里返回的是一個(gè)函數(shù) const greet1 = greet("小紅"); console.log(greet1) /** * ? () { console.log(`Hello, ${name}!`); } */ // 執(zhí)行 函數(shù) greet1(); // 輸出:Hello, 小紅!
在這個(gè)例子中,`greet` 函數(shù)接收一個(gè)參數(shù) `name`,并返回一個(gè)匿名函數(shù)。這個(gè)匿名函數(shù)在執(zhí)行時(shí),會(huì)訪問(wèn)其創(chuàng)建時(shí)所在的作用域鏈中的變量 `name`。當(dāng)我們調(diào)用 `greet("小紅")` 時(shí),返回的匿名函數(shù)就“捕獲”了變量 `name` 的值 `"Alice"`,并將其保存在閉包中。因此,當(dāng)我們調(diào)用 `greet()` 時(shí),它就會(huì)輸出 `"Hello, 小紅!"`。這就是閉包的神奇之處,它讓函數(shù)能夠記住并訪問(wèn)其創(chuàng)建時(shí)的變量。
三、閉包的應(yīng)用場(chǎng)景
閉包的應(yīng)用場(chǎng)景非常廣泛,從簡(jiǎn)單的問(wèn)候函數(shù)到復(fù)雜的事件監(jiān)聽器、延遲任務(wù)和緩存功能,都可以看到閉包的身影。以下是一些常見的應(yīng)用場(chǎng)景:
1.封裝私有變量
閉包可以用來(lái)封裝私有變量,實(shí)現(xiàn)數(shù)據(jù)的封裝和隱藏。例如,我們可以創(chuàng)建一個(gè)用戶對(duì)象,通過(guò)閉包來(lái)封裝用戶的姓名和年齡:
function createUser(name, age) { return { getName: function () { return name; // 訪問(wèn)閉包中的變量 }, getAge: function () { return age; // 訪問(wèn)閉包中的變量 }, setAge: function (newAge) { age = newAge; // 修改閉包中的變量 } }; } const user = createUser("小明", 25); console.log(user.getName()); // 輸出:小明 console.log(user.getAge()); // 輸出:25 user.setAge(26); console.log(user.getAge()); // 輸出:26
在這個(gè)例子中,`createUser` 函數(shù)通過(guò)閉包封裝了用戶的姓名和年齡。外部代碼無(wú)法直接訪問(wèn)這些私有變量,只能通過(guò) `getName`、`getAge` 和 `setAge` 方法來(lái)獲取和修改它們的值。這種封裝方式不僅保證了數(shù)據(jù)的安全性,還提供了靈活的接口供外部代碼使用。
2.創(chuàng)建獨(dú)立的計(jì)數(shù)器
閉包可以用來(lái)創(chuàng)建獨(dú)立的計(jì)數(shù)器,每個(gè)計(jì)數(shù)器都有自己的狀態(tài),互不影響。例如:
function createCounter() { let count = 0; // 閉包中的狀態(tài)值 return function () { count += 1; // 每次調(diào)用時(shí)遞增 console.log(`Count: ${count}`); }; } const counter1 = createCounter(); counter1(); // 輸出:Count: 1 counter1(); // 輸出:Count: 2 counter2(); // 輸出:Count: 1 counter2(); // 輸出:Count: 2
在這個(gè)例子中,每次調(diào)用 `createCounter` 函數(shù)時(shí),都會(huì)創(chuàng)建一個(gè)新的閉包,其中包含一個(gè)獨(dú)立的計(jì)數(shù)器狀態(tài) `count`。因此,`counter1` 和 `counter2` 是兩個(gè)獨(dú)立的計(jì)數(shù)器,它們的計(jì)數(shù)互不影響。
3.實(shí)現(xiàn)延遲任務(wù)
閉包還可以用來(lái)實(shí)現(xiàn)延遲任務(wù),例如:
// task:這是一個(gè)函數(shù),表示需要延遲執(zhí)行的任務(wù)。 // delay:這是一個(gè)數(shù)字,表示延遲的時(shí)間(單位為毫秒)。 function createDelayedTask(task, delay) { // 在函數(shù)內(nèi)部,定義了一個(gè)變量 timeoutId,用于存儲(chǔ) setTimeout 返回的定時(shí)器 ID。 // 這個(gè)變量被閉包捕獲,因此可以在返回的對(duì)象方法中訪問(wèn)和修改它。 let timeoutId; return { run: function () { timeoutId = setTimeout(task, delay); }, cancel: function () { clearTimeout(timeoutId); console.log("任務(wù)取消"); } }; } // createDelayedTask 返回一個(gè)對(duì)象,包含 run 和 cancel 方法, // 并將其賦值給變量 delayedTask。 // task:一個(gè)匿名箭頭函數(shù) () => console.log("執(zhí)行任務(wù)"),表示需要延遲執(zhí)行的任務(wù)。 // delay:延遲時(shí)間為 2000 毫秒(即 2 秒)。 const delayedTask = createDelayedTask(() => console.log("執(zhí)行任務(wù)"), 2000); // 在內(nèi)部,setTimeout 被調(diào)用,將傳入的任務(wù)函數(shù) () => console.log("執(zhí)行任務(wù)") 設(shè)置為在 2 秒后執(zhí)行。 delayedTask.run(); // 啟動(dòng)任務(wù) /** 這里又調(diào)用了一個(gè) setTimeout,將 delayedTask.cancel 方法設(shè)置為在 1 秒后執(zhí)行。 當(dāng) delayedTask.cancel 被調(diào)用時(shí): clearTimeout(timeoutId) 被執(zhí)行,清除之前設(shè)置的定時(shí)器(即取消延遲任務(wù))。 打印消息 "任務(wù)取消"。*/ setTimeout(delayedTask.cancel, 1000); // 在 1 秒后取消任務(wù)
在這個(gè)例子中,`createDelayedTask` 函數(shù)通過(guò)閉包封裝了延遲任務(wù)的邏輯和狀態(tài)。`run` 方法用于啟動(dòng)任務(wù),`cancel` 方法用于取消任務(wù)。通過(guò)閉包,我們可以將任務(wù)的狀態(tài)(如 `timeoutId`)保存起來(lái),方便在需要時(shí)進(jìn)行操作。
運(yùn)行過(guò)程總結(jié)
時(shí)間線:
- 0 秒:調(diào)用
delayedTask.run()
,設(shè)置一個(gè)定時(shí)器,計(jì)劃在 2 秒后執(zhí)行任務(wù)(打印"執(zhí)行任務(wù)"
)。 - 1 秒:調(diào)用
delayedTask.cancel()
,清除定時(shí)器,取消任務(wù)。 - 2 秒:原計(jì)劃的任務(wù)不會(huì)執(zhí)行,因?yàn)槎〞r(shí)器已被清除。
輸出結(jié)果:
- 在 1 秒后,控制臺(tái)會(huì)輸出
"任務(wù)取消"
。 - 2 秒后,不會(huì)輸出
"執(zhí)行任務(wù)"
,因?yàn)槿蝿?wù)已被取消。
這種模式在實(shí)際開發(fā)中非常有用,例如:
- 在用戶操作頻繁的場(chǎng)景下,避免重復(fù)觸發(fā)某些操作(如搜索框的防抖功能)。
- 在需要延遲執(zhí)行任務(wù)但可能需要取消任務(wù)的場(chǎng)景中(如用戶取消操作或超時(shí)取消任務(wù))。
4.實(shí)現(xiàn)緩存功能
閉包還可以用來(lái)實(shí)現(xiàn)緩存功能,例如:
function createCache() { const cache = {}; // 閉包中的緩存對(duì)象 return { get: function (key) { return cache[key]; }, set: function (key, value) { cache[key] = value; } }; } const myCache = createCache(); myCache.set("name", "小軍"); console.log(myCache.get("name")); // 輸出:小軍
在這個(gè)例子中,`createCache` 函數(shù)通過(guò)閉包封裝了一個(gè)緩存對(duì)象 `cache`。通過(guò) `set` 方法可以將數(shù)據(jù)存儲(chǔ)到緩存中,通過(guò) `get` 方法可以從緩存中獲取數(shù)據(jù)。這種緩存功能在實(shí)際開發(fā)中非常有用,可以提高程序的性能。
5.leetcode.2715執(zhí)行可取消函數(shù)的解法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ArrayWrapper Example</title> </head> <body> <script> /** * 創(chuàng)建一個(gè)可取消的函數(shù)執(zhí)行器 * @param {Function} fn - 需要延遲執(zhí)行的函數(shù) * @param {Array} args - 傳遞給 fn 的參數(shù)數(shù)組 * @param {number} t - 延遲時(shí)間(毫秒) * @return {Function} - 返回一個(gè)取消函數(shù),用于取消延遲執(zhí)行 */ var cancellable = function (fn, args, t) { let timeOutId; // 用于存儲(chǔ) setTimeout 返回的定時(shí)器 ID // 取消函數(shù),用于清除定時(shí)器 function cancelFn() { clearTimeout(timeOutId); // 清除定時(shí)器,阻止 fn 執(zhí)行 } // 設(shè)置定時(shí)器,延遲 t 毫秒后執(zhí)行 fn timeOutId = setTimeout(() => { fn(...args); // 使用展開運(yùn)算符將 args 作為參數(shù)傳遞給 fn }, t); // 返回取消函數(shù),供外部調(diào)用 return cancelFn; }; // 用于存儲(chǔ)執(zhí)行結(jié)果的數(shù)組 const result = []; // 示例函數(shù),將輸入?yún)?shù)乘以 5 const fn = (x) => x * 5; // 示例函數(shù)的參數(shù)和延遲時(shí)間 const args = [2], t = 20, cancelTimeMs = 50; // 記錄開始時(shí)間,用于計(jì)算延遲執(zhí)行的時(shí)間差 const start = performance.now(); // 日志函數(shù),記錄函數(shù)執(zhí)行的時(shí)間和返回值 const log = (...argsArr) => { const diff = Math.floor(performance.now() - start); // 計(jì)算從開始到現(xiàn)在的毫秒數(shù) result.push({ "time": diff, "returned": fn(...argsArr) }); // 將執(zhí)行時(shí)間和返回值存入 result }; // 創(chuàng)建一個(gè)可取消的延遲任務(wù) const cancel = cancellable(log, args, t); // 在 cancelTimeMs 毫秒后調(diào)用取消函數(shù),取消延遲任務(wù) const maxT = Math.max(t, cancelTimeMs); // 計(jì)算延遲時(shí)間和取消時(shí)間的最大值 setTimeout(cancel, cancelTimeMs); // 在延遲任務(wù)和取消任務(wù)之后,打印結(jié)果 setTimeout(() => { console.log(result); // [{"time":20,"returned":10}] }, maxT + 15); // 確保在所有任務(wù)完成后打印結(jié)果 </script> </body> </html>
詳細(xì)解釋:
設(shè)置定時(shí)器:
timeoutId = setTimeout(() => { fn(...args); // 使用 args 作為參數(shù)執(zhí)行 fn }, t);
這里使用 setTimeout
設(shè)置了一個(gè)定時(shí)器,延遲 t
毫秒后執(zhí)行 fn(...args)
。
setTimeout
返回一個(gè)唯一的 timeoutId
,這個(gè) ID 用于后續(xù)的取消操作。
2.定義取消函數(shù):
function cancelFn() { clearTimeout(timeoutId); // 清除定時(shí)器,取消 fn 的執(zhí)行 }
cancelFn
是一個(gè)函數(shù),它的作用是調(diào)用 clearTimeout(timeoutId)
。
如果在 fn
執(zhí)行之前調(diào)用了 cancelFn
,clearTimeout
會(huì)取消對(duì)應(yīng)的定時(shí)器,fn
就不會(huì)被執(zhí)行。
返回取消函數(shù):
return cancelFn;
返回 cancelFn
是為了讓調(diào)用者能夠在需要的時(shí)候調(diào)用它。
如果調(diào)用者沒(méi)有調(diào)用 cancelFn
,定時(shí)器會(huì)正常觸發(fā),fn
會(huì)在延遲時(shí)間 t
后執(zhí)行。
如果調(diào)用者調(diào)用了 cancelFn
,clearTimeout
會(huì)取消定時(shí)器,fn
就不會(huì)被執(zhí)行。
. setTimeout
和 clearTimeout
的工作機(jī)制
setTimeout
:設(shè)置一個(gè)定時(shí)器,延遲 t
毫秒后執(zhí)行某個(gè)函數(shù)。它返回一個(gè)定時(shí)器的 ID(timeoutId
),這個(gè) ID 用于后續(xù)的取消操作。
clearTimeout
:通過(guò)傳入定時(shí)器的 ID 來(lái)取消對(duì)應(yīng)的定時(shí)器。如果定時(shí)器已經(jīng)被觸發(fā)(即回調(diào)函數(shù)已經(jīng)開始執(zhí)行),clearTimeout
將不會(huì)有任何效果。
四、閉包的注意事項(xiàng)
雖然閉包非常強(qiáng)大,但在使用時(shí)也需要小心一些潛在的問(wèn)題。例如,閉包可能會(huì)導(dǎo)致內(nèi)存泄漏,因?yàn)殚]包會(huì)一直保存其創(chuàng)建時(shí)的作用域鏈中的變量,即使這些變量不再被使用,也不會(huì)被垃圾回收器回收。因此,在使用閉包時(shí),我們需要確保及時(shí)釋放不再使用的變量,避免內(nèi)存泄漏。
五、總結(jié)
閉包是JavaScript中一個(gè)非常重要的特性,它通過(guò)函數(shù)返回函數(shù)的方式,讓函數(shù)能夠記住并訪問(wèn)其創(chuàng)建時(shí)所在的作用域鏈中的變量。閉包不僅可以封裝私有變量,實(shí)現(xiàn)數(shù)據(jù)的封裝和隱藏,還可以創(chuàng)建獨(dú)立的計(jì)數(shù)器、實(shí)現(xiàn)延遲任務(wù)和緩存功能等。在實(shí)際開發(fā)中,閉包的應(yīng)用場(chǎng)景非常廣泛,掌握閉包的使用方法,可以讓你的代碼更加靈活和強(qiáng)大。當(dāng)然,在使用閉包時(shí),我們也要注意避免內(nèi)存泄漏等問(wèn)題,合理地使用閉包,才能充分發(fā)揮其優(yōu)勢(shì)。
以上就是JavaScript閉包實(shí)現(xiàn)函數(shù)返回函數(shù)詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript函數(shù)返回函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js實(shí)現(xiàn)base64、url和blob之間相互轉(zhuǎn)換的三種方式
Blob對(duì)象表示一個(gè)不可變、原始數(shù)據(jù)的類文件對(duì)象,Blob表示的不一定是JavaScript原生格式的數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于js實(shí)現(xiàn)base64、url和blob之間相互轉(zhuǎn)換的三種方式,需要的朋友可以參考下2023-04-04layui實(shí)現(xiàn)把數(shù)據(jù)表格時(shí)間戳轉(zhuǎn)換為時(shí)間格式的例子
今天小編就為大家分享一篇layui實(shí)現(xiàn)把數(shù)據(jù)表格時(shí)間戳轉(zhuǎn)換為時(shí)間格式的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09JavaScript基礎(chǔ)系列之函數(shù)和方法詳解
經(jīng)常談?wù)撈鸷瘮?shù)和方法,也常常搞不清楚它們之間的界限,經(jīng)常把兩個(gè)混用,這篇文章主要給大家介紹了關(guān)于JavaScript基礎(chǔ)系列之函數(shù)和方法的相關(guān)資料,需要的朋友可以參考下2021-09-09js中利用cookie實(shí)現(xiàn)記住密碼功能
這篇文章主要為大家詳細(xì)介紹了js中利用cookie實(shí)現(xiàn)記住密碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10