js閉包和垃圾回收機制示例詳解
前言
閉包和垃圾回收機制常常作為前端學習開發(fā)中的難點,也經(jīng)常在面試中遇到這樣的問題,本文記錄一下在學習工作中關(guān)于這方面的筆記。
正文
1.閉包
閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)。作為一個JavaScript開發(fā)者,理解閉包十分重要。
1.1閉包是什么?
閉包就是一個函數(shù)引用另一個函數(shù)的變量,內(nèi)部函數(shù)被返回到外部并保存時產(chǎn)生,(內(nèi)部函數(shù)的作用域鏈AO使用了外層函數(shù)的AO)
因為變量被引用著所以不會被回收,因此可以用來封裝一個私有變量,但是不必要的閉包只會增加內(nèi)存消耗。
閉包是一種保護私有變量的機制,在函數(shù)執(zhí)行時形成私有的作用域,保護里面的私有變量不受外界干擾?;蛘哒f閉包就是子函數(shù)可以使用父函數(shù)的局部變量,還有父函數(shù)的參數(shù)。
1.2閉包的特性
①函數(shù)嵌套函數(shù)
②函數(shù)內(nèi)部可以引用函數(shù)外部的參數(shù)和變量
?、蹍?shù)和變量不會被垃圾回收機制回收
1.3理解閉包
基于我們所熟悉的作用域鏈相關(guān)知識,我們來看下關(guān)于計數(shù)器的問題,如何實現(xiàn)一個函數(shù),每次調(diào)用該函數(shù)時候計數(shù)器加一。
var counter=0; function demo3(){ console.log(counter+=1); } demo3();//1 demo3();//2 var counter=5; demo3(); //6
上面的方法,如果在任何一個地方改變counter的值 計數(shù)器都會失效,javascript解決這種問題用到閉包,就是函數(shù)內(nèi)部內(nèi)嵌函數(shù),再來看下利用閉包如何實現(xiàn)。
function add() { var counter = 0; return function plus() { counter += 1; return counter } } var count=add() console.log(count())//1 var counter=100 console.log(count())//2
上面就是一個閉包使用的實例 ,函數(shù)add內(nèi)部內(nèi)嵌一個plus函數(shù),count變量引用該返回的函數(shù),每次外部函數(shù)add執(zhí)行的時候都會開辟一塊內(nèi)存空間,外部函數(shù)的地址不同,都會重新創(chuàng)建一個新的地址,把plus函數(shù)嵌套在add函數(shù)內(nèi)部,這樣就產(chǎn)生了counter這個局部變量,內(nèi)次調(diào)用count函數(shù),該局部變量值加一,從而實現(xiàn)了真正的計數(shù)器問題。
1.4閉包的主要實現(xiàn)形式
這里主要通過兩種形式來學習閉包:
①函數(shù)作為返回值,也就是上面的例子中用到的。
function showName(){ var name="xiaoming" return function(){ return name } } var name1=showName() console.log(name1())
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
?、陂]包作為參數(shù)傳遞
var num = 15 var foo = function(x){ if(x>num){ console.log(x) } } function foo2(fnc){ var num=30 fnc(25) } foo2(foo)//25
上面這段代碼中,函數(shù)foo作為參數(shù)傳入到函數(shù)foo2中,在執(zhí)行foo2的時候,25作為參數(shù)傳入foo中,這時判斷的x>num的num取值是創(chuàng)建函數(shù)的作用域中的num,即全局的num,而不是foo2內(nèi)部的num,因此打印出了25。
1.5閉包的優(yōu)缺點
優(yōu)點:
?、俦Wo函數(shù)內(nèi)的變量安全 ,實現(xiàn)封裝,防止變量流入其他環(huán)境發(fā)生命名沖突
?、谠趦?nèi)存中維持一個變量,可以做緩存(但使用多了同時也是一項缺點,消耗內(nèi)存)
?、勰涿詧?zhí)行函數(shù)可以減少內(nèi)存消耗
缺點:
①其中一點上面已經(jīng)有體現(xiàn)了,就是被引用的私有變量不能被銷毀,增大了內(nèi)存消耗,造成內(nèi)存泄漏,解決方法是可以在使用完變量后手動為它賦值為null;
?、谄浯斡捎陂]包涉及跨域訪問,所以會導致性能損失,我們可以通過把跨作用域變量存儲在局部變量中,然后直接訪問局部變量,來減輕對執(zhí)行速度的影響。
1.6閉包的使用
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log( i); }, 1000); } console.log(i);
我們來看上面的問題,這是一道很常見的題,可這道題會輸出什么,一般人都知道輸出結(jié)果是 5,5,5,5,5,5,你仔細觀察可能會發(fā)現(xiàn)這道題還有很多巧妙之處,這6個5的輸出順序具體是怎樣的?5 -> 5,5,5,5,5 ,了解同步異步的人也不難理解這種情況,基于上面的問題,接下來思考如何實現(xiàn)5 -> 0,1,2,3,4這樣的順序輸出呢?
for (var i = 0; i < 5; i++) { (function(j) { // j = i setTimeout(function() { console.log( j); }, 1000); })(i); } console.log( i); //5 -> 0,1,2,3,4
這樣在for循環(huán)種加入匿名函數(shù),匿名函數(shù)入?yún)⑹敲看蔚膇的值,在同步函數(shù)輸出5的一秒之后,繼續(xù)輸出01234。
for (var i = 0; i < 5; i++) { setTimeout(function(j) { console.log(j); }, 1000, i); } console.log( i); //5 -> 0,1,2,3,4
仔細查看setTimeout的api你會發(fā)現(xiàn)它還有第三個參數(shù),這樣就省去了通過匿名函數(shù)傳入i的問題。
var output = function (i) { setTimeout(function() { console.log(i); }, 1000); }; for (var i = 0; i < 5; i++) { output(i); // 這里傳過去的 i 值被復(fù)制了 } console.log(i); //5 -> 0,1,2,3,4
這里就是利用閉包將函數(shù)表達式作為參數(shù)傳遞到for循環(huán)中,同樣實現(xiàn)了上述效果。
for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(new Date, i); }, 1000); } console.log(new Date, i); //5 -> 0,1,2,3,4
知道let塊級作用域的人會想到上面的方法。但是如果要實現(xiàn)0 -> 1 -> 2 -> 3 -> 4 -> 5這樣的效果呢。
for (var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(new Date, j); }, 1000 * j); // 這里修改 0~4 的定時器時間 })(i); } setTimeout(function() { // 這里增加定時器,超時設(shè)置為 5 秒 console.log(new Date, i); }, 1000 * i); //0 -> 1 -> 2 -> 3 -> 4 -> 5
還有下面的代碼,通過promise來實現(xiàn)。
const tasks = []; for (var i = 0; i < 5; i++) { // 這里 i 的聲明不能改成 let,如果要改該怎么做? ((j) => { tasks.push(new Promise((resolve) => { setTimeout(() => { console.log(new Date, j); resolve(); // 這里一定要 resolve,否則代碼不會按預(yù)期 work }, 1000 * j); // 定時器的超時時間逐步增加 })); })(i); } Promise.all(tasks).then(() => { setTimeout(() => { console.log(new Date, i); }, 1000); // 注意這里只需要把超時設(shè)置為 1 秒 }); //0 -> 1 -> 2 -> 3 -> 4 -> 5
const tasks = []; // 這里存放異步操作的 Promise const output = (i) => new Promise((resolve) => { setTimeout(() => { console.log(new Date, i); resolve(); }, 1000 * i); }); // 生成全部的異步操作 for (var i = 0; i < 5; i++) { tasks.push(output(i)); } // 異步操作完成之后,輸出最后的 i Promise.all(tasks).then(() => { setTimeout(() => { console.log(new Date, i); }, 1000); }); //0 -> 1 -> 2 -> 3 -> 4 -> 5
// 模擬其他語言中的 sleep,實際上可以是任何異步操作 const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => { // 聲明即執(zhí)行的 async 函數(shù)表達式 for (var i = 0; i < 5; i++) { if (i > 0) { await sleep(1000); } console.log(new Date, i); } await sleep(1000); console.log(new Date, i); })(); //0 -> 1 -> 2 -> 3 -> 4 -> 5
上面的代碼中都用到了閉包,總之,閉包找到的是同一地址中父級函數(shù)中對應(yīng)變量最終的值。
2.垃圾回收機制
JavaScript 中的內(nèi)存管理是自動執(zhí)行的,而且是不可見的。我們創(chuàng)建基本類型、對象、函數(shù)……所有這些都需要內(nèi)存。
通常用采用的垃圾回收有兩種方法:標記清除、引用計數(shù)。
1、標記清除
垃圾收集器在運行的時候會給存儲在內(nèi)存中的所有變量都加上標記。然后,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標記。
而在此之后再被加上標記的變量將被視為準備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了。
最后。垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標記的值,并回收他們所占用的內(nèi)存空間
2.引用計數(shù)
引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)。當聲明了一個變量并將一個引用類型賦值給該變量時,則這個值的引用次數(shù)就是1。
相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數(shù)就減1。當這個引用次數(shù)變成0時,
則說明沒有辦法再訪問這個值了,因而就可以將其所占的內(nèi)存空間給收回來。這樣,垃圾收集器下次再運行時,
它就會釋放那些引用次數(shù)為0的值所占的內(nèi)存。
總結(jié)
到此這篇關(guān)于js閉包和垃圾回收機制的文章就介紹到這了,更多相關(guān)js閉包和垃圾回收內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中輕松獲取頁面參數(shù)值的N種方法(含代碼示例)
本文旨在深入淺出地揭示如何在JavaScript中巧妙提取那些隱藏在URL背后的寶貴信息,從基礎(chǔ)方法到高級技巧,一網(wǎng)打盡,無論你是編程界的菜鳥還是久經(jīng)沙場的老將,這里都有值得你品鑒的“珍饈”,需要的朋友可以參考下2024-06-06微信小程序?qū)崿F(xiàn)滑動切換自定義頁碼的方法分析
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)滑動切換自定義頁碼的方法,結(jié)合實例形式分析了微信小程序頁碼動態(tài)切換相關(guān)實現(xiàn)技巧與注意事項,需要的朋友可以參考下2018-12-12JS利用window.print()實現(xiàn)網(wǎng)頁打印功能
print作為瀏覽已經(jīng)比較成熟的技術(shù)可以經(jīng)常被用來打印頁面的部分內(nèi)容。本文將在JS中調(diào)用window.print()方法實現(xiàn)網(wǎng)頁打印功能,感興趣的可以跟隨小編一起學習一下2022-04-04JS 非圖片動態(tài)loading效果實現(xiàn)代碼
功能說明:譬如在按某個button時,顯示消息"Loading”,然后每隔一秒后后面加上".",至一定數(shù)量的"."時如:"Loading...",再重置此消息為"Loading",繼續(xù)動態(tài)顯示,直至按鈕事件處理完成。2010-04-04