JavaScript中閉包的作用和應(yīng)用場景
閉包是什么?
- 閉包是指一個(gè)函數(shù)可以訪問并操作其詞法作用域外的變量的能力。
- 閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
- 例如在javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以閉包可以理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)“。
- 在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來的橋梁。
特點(diǎn):函數(shù)嵌套,并返回子函數(shù),子函數(shù)訪問了外變量。
//外部函數(shù)
function outerFunction() {
//內(nèi)部函數(shù)外的變量
var outerVariable = 'I am from outer function';
//返回內(nèi)部函數(shù)
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
// outerFunction執(zhí)行完之后被銷毀,但是outerVariable被引用著所以仍然活著
var closure = outerFunction();
closure(); // 輸出:I am from outer function閉包的作用?
- 封裝私有變量:閉包可以用于創(chuàng)建私有變量和方法。通過在函數(shù)內(nèi)部定義變量,并返回一個(gè)內(nèi)部函數(shù),外部無法直接訪問這些變量,從而實(shí)現(xiàn)了封裝的效果。這樣可以避免全局變量的污染,提高代碼的可維護(hù)性和安全性。
- 延遲執(zhí)行:閉包可以用于實(shí)現(xiàn)延遲執(zhí)行的效果。通過在函數(shù)內(nèi)部定義一個(gè)定時(shí)器或事件監(jiān)聽器,并返回一個(gè)內(nèi)部函數(shù),可以在需要的時(shí)候觸發(fā)執(zhí)行。
- 記憶化/保持狀態(tài):閉包可以用于實(shí)現(xiàn)記憶化的效果,即將函數(shù)的計(jì)算結(jié)果緩存起來,以便在后續(xù)調(diào)用時(shí)直接返回緩存的結(jié)果,提高函數(shù)的執(zhí)行效率。
- 回調(diào)函數(shù):閉包可以用于實(shí)現(xiàn)回調(diào)函數(shù)。通過將一個(gè)函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù),并在內(nèi)部函數(shù)中調(diào)用該函數(shù),可以實(shí)現(xiàn)異步操作的回調(diào)機(jī)制。
- 模塊化開發(fā):閉包可以用于實(shí)現(xiàn)模塊化開發(fā)。通過將一組相關(guān)的變量和方法封裝在一個(gè)閉包中,可以避免全局命名空間的污染,實(shí)現(xiàn)模塊的獨(dú)立性和復(fù)用性。
閉包的缺陷?
- 內(nèi)存占用:閉包會(huì)導(dǎo)致外部函數(shù)的變量無法被垃圾回收,從而增加內(nèi)存占用。如果閉包會(huì)長時(shí)間存在,那么外部變量將無法被釋放,可能導(dǎo)致內(nèi)存泄漏。
- 性能損耗:閉包涉及到作用域鏈的查找過程,會(huì)帶來一定的性能損耗。在性能要求高的場景下,需要注意閉包的使用。
閉包的應(yīng)用場景?
參考鏈接:js閉包的6種應(yīng)用場景總結(jié)_javascript技巧_腳本之家 (jb51.net)
1. 自執(zhí)行函數(shù)(可以實(shí)現(xiàn)單例模式)
let say = (function(){
let val = 'hello world';
function say(){
console.log(val);
}
return say;
})()var Singleton = (function () {
var instance;
function createInstance() {
var object = new Object("I am the instance");
return object;
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();2. 防抖節(jié)流
// 節(jié)流函數(shù)封裝
function throttle(func, delay) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, delay);
}
};
}
// 防抖函數(shù)封裝
function debounce(func, delay) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}3. 函數(shù)柯里化
//柯里化前
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); //6
//柯里化后
function addCurried1(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
//箭頭函數(shù)簡寫
const addCurried2 = (a) => (b) => (c) => a + b + c;
console.log(addCurried1(1)(2)(3)); //6
console.log(addCurried2(1)(2)(3)); //64. 發(fā)布訂閱
function createPubSub() {
// 存儲(chǔ)事件及其對應(yīng)的訂閱者
const subscribers = {};
// 訂閱事件
function subscribe(event, callback) {
// 如果事件不存在,則創(chuàng)建一個(gè)新的空數(shù)組
if (!subscribers[event]) {
subscribers[event] = [];
}
// 將回調(diào)函數(shù)添加到訂閱者數(shù)組中
subscribers[event].push(callback);
}
// 發(fā)布事件
function publish(event, data) {
// 如果事件不存在,則直接返回
if (!subscribers[event]) {
return;
}
// 遍歷訂閱者數(shù)組,調(diào)用每個(gè)訂閱者的回調(diào)函數(shù)
subscribers[event].forEach((callback) => {
callback(data);
});
}
// 返回訂閱和發(fā)布函數(shù)
return {
subscribe,
publish,
};
}
// 使用示例
const pubSub = createPubSub();
// 訂閱事件
pubSub.subscribe("event1", (data) => {
console.log("訂閱者1收到事件1的數(shù)據(jù):", data);
});
pubSub.subscribe("event2", (data) => {
console.log("訂閱者2收到事件2的數(shù)據(jù):", data);
});
// 發(fā)布事件
pubSub.publish("event1", "Hello");
// 輸出: 訂閱者1收到事件1的數(shù)據(jù): Hello
pubSub.publish("event2", "World");
// 輸出: 訂閱者2收到事件2的數(shù)據(jù): World5. 迭代器
function createIterator(arr) {
let index = 0;
return {
next: function() {
if (index < arr.length) {
return {
value: arr[index++],
done: false
};
} else {
return {
done: true
};
}
}
};
}
const myIterator = createIterator([1, 2, 3]);
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { done: true }如何釋放閉包?
釋放閉包通常是通過解除對閉包的引用來實(shí)現(xiàn)的。當(dāng)不再需要使用閉包時(shí),可以將閉包所在的變量設(shè)置為 null 或者將閉包所在的變量賦予其他值,從而斷開對閉包的引用。這樣,JavaScript 引擎在下一次垃圾回收時(shí)會(huì)判斷閉包不再被引用,從而釋放閉包占用的內(nèi)存空間。
相關(guān)概念
函數(shù)的詞法作用域(也叫靜態(tài)作用域/閉包作用域)
是指在函數(shù)定義時(shí)確定的作用域,而不是在函數(shù)調(diào)用時(shí)確定的作用域。它是由函數(shù)在定義時(shí)所處的上下文環(huán)境決定的,與函數(shù)的調(diào)用位置無關(guān)。
規(guī)則 函數(shù)內(nèi)部可以訪問函數(shù)外部的變量 函數(shù)內(nèi)部的變量在函數(shù)外部不可訪問 函數(shù)內(nèi)部可以訪問函數(shù)外部的函數(shù) 函數(shù)內(nèi)部的函數(shù)可以訪問外部函數(shù)的變量
詞法作用域的優(yōu)勢在于它提供了更可靠和可預(yù)測的變量訪問方式。在函數(shù)定義時(shí),就確定了函數(shù)內(nèi)部可以訪問的變量,不會(huì)受到函數(shù)調(diào)用位置的影響。這種靜態(tài)作用域的特性使得代碼更易于理解和維護(hù),并且可以實(shí)現(xiàn)一些高級的編程技巧,如閉包和模塊化開發(fā)。
執(zhí)行上下文
每當(dāng) JavaScript 代碼執(zhí)行時(shí),都會(huì)創(chuàng)建一個(gè)執(zhí)行上下文(是 JavaScript 引擎內(nèi)部的一種數(shù)據(jù)結(jié)構(gòu),用于管理代碼的執(zhí)行環(huán)境、變量的作用域),并按照特定的規(guī)則進(jìn)行管理和銷毀。
執(zhí)行上下文可以分為三種類型:
- 全局執(zhí)行上下文(Global Execution Context):全局執(zhí)行上下文是在整個(gè)腳本文件執(zhí)行時(shí)創(chuàng)建的,它是最外層的執(zhí)行上下文。在全局執(zhí)行上下文中,變量和函數(shù)聲明會(huì)被提升,并且會(huì)創(chuàng)建全局對象(如瀏覽器環(huán)境中的 window 對象)和全局變量。
- 函數(shù)執(zhí)行上下文(Function Execution Context):每當(dāng)函數(shù)被調(diào)用時(shí),都會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文。函數(shù)執(zhí)行上下文中包含了函數(shù)的參數(shù)、局部變量、函數(shù)內(nèi)部的變量和函數(shù)聲明。每個(gè)函數(shù)執(zhí)行上下文都有自己的作用域鏈和 this 值。
- Eval 執(zhí)行上下文(Eval Execution Context):在使用 eval() 函數(shù)執(zhí)行代碼時(shí),會(huì)創(chuàng)建一個(gè) eval 執(zhí)行上下文。它與全局執(zhí)行上下文類似,但是它有自己的詞法作用域。
執(zhí)行上下文的生命周期包括以下階段:
- 創(chuàng)建階段(Creation Phase):在這個(gè)階段,JavaScript 引擎會(huì)創(chuàng)建執(zhí)行上下文,并進(jìn)行變量和函數(shù)的聲明。變量會(huì)被初始化為 undefined,函數(shù)會(huì)被存儲(chǔ)在內(nèi)存中。
- 執(zhí)行階段(Execution Phase):在這個(gè)階段,JavaScript 引擎會(huì)按照代碼的順序執(zhí)行語句,給變量賦值,執(zhí)行函數(shù)調(diào)用等操作。
觀察和理解執(zhí)行上下文
執(zhí)行上下文的管理和切換由 JavaScript 引擎自動(dòng)完成,開發(fā)者可以通過了解執(zhí)行上下文的概念和規(guī)則,更好地理解代碼的執(zhí)行過程,以及變量和函數(shù)的作用范圍。
盡管無法直接訪問或輸出執(zhí)行上下文,但我們可以通過一些間接的方式來觀察和理解執(zhí)行上下文的行為和特性:
- 使用 console.log():可以在代碼中使用 console.log() 方法輸出變量的值、函數(shù)的執(zhí)行結(jié)果等信息,從而間接觀察到執(zhí)行上下文的影響。
- 使用調(diào)試工具:現(xiàn)代的瀏覽器和開發(fā)工具提供了強(qiáng)大的調(diào)試功能,可以在代碼執(zhí)行過程中查看執(zhí)行上下文的變化、變量的值以及調(diào)用棧等信息。
- 使用閉包:通過創(chuàng)建閉包,我們可以間接地觀察到執(zhí)行上下文的作用。閉包可以讓內(nèi)部函數(shù)訪問外部函數(shù)的變量,從而形成一個(gè)包含外部執(zhí)行上下文的閉包執(zhí)行上下文。
以上就是JavaScript中閉包的作用和應(yīng)用場景的詳細(xì)內(nèi)容,更多關(guān)于JavaScript閉包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Js-$.extend擴(kuò)展方法使方法參數(shù)更靈活
在JS里,我們的方法參數(shù)通常使用JQ的$.extend擴(kuò)展方法來實(shí)現(xiàn),感興趣的朋友可以了解下2013-01-01
Javascript實(shí)現(xiàn)的分頁函數(shù)
Javascript實(shí)現(xiàn)的分頁函數(shù)...2007-02-02
JavaScript實(shí)現(xiàn)多球運(yùn)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)多球運(yùn)動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09
風(fēng)吟的小型JavaScirpt庫 (FY.JS).
此庫非常的迷你壓縮之后只有1.54KB.但是卻有類似jquery的語法有COOKIE操作還有DOM以及AJAX跟綁定事件函數(shù).2010-03-03
js和jQuery以及easyui實(shí)現(xiàn)對下拉框的指定賦值方法
下面小編就為大家分享一篇js和jQuery以及easyui實(shí)現(xiàn)對下拉框的指定賦值方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01

