js--閉包與垃圾回收機(jī)制
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 丶Serendipity丶
來源 | urlify.cn/Jj2EJz
76套java從入門到精通實(shí)戰(zhàn)課程分享
前言
閉包和垃圾回收機(jī)制常常作為前端學(xué)習(xí)開發(fā)中的難點(diǎn),也經(jīng)常在面試中遇到這樣的問題,本文記錄一下在學(xué)習(xí)工作中關(guān)于這方面的筆記。
正文
1.閉包
閉包(closure)是Javascript語言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。作為一個(gè)JavaScript開發(fā)者,理解閉包十分重要。
1.1閉包是什么?
閉包就是一個(gè)函數(shù)引用另一個(gè)函數(shù)的變量,內(nèi)部函數(shù)被返回到外部并保存時(shí)產(chǎn)生,(內(nèi)部函數(shù)的作用域鏈AO使用了外層函數(shù)的AO)
因?yàn)樽兞勘灰弥圆粫?huì)被回收,因此可以用來封裝一個(gè)私有變量,但是不必要的閉包只會(huì)增加內(nèi)存消耗。
閉包是一種保護(hù)私有變量的機(jī)制,在函數(shù)執(zhí)行時(shí)形成私有的作用域,保護(hù)里面的私有變量不受外界干擾?;蛘哒f閉包就是子函數(shù)可以使用父函數(shù)的局部變量,還有父函數(shù)的參數(shù)。
1.2閉包的特性
①函數(shù)嵌套函數(shù)
?、诤瘮?shù)內(nèi)部可以引用函數(shù)外部的參數(shù)和變量
?、蹍?shù)和變量不會(huì)被垃圾回收機(jī)制回收
1.3理解閉包
基于我們所熟悉的作用域鏈相關(guān)知識(shí),我們來看下關(guān)于計(jì)數(shù)器的問題,如何實(shí)現(xiàn)一個(gè)函數(shù),每次調(diào)用該函數(shù)時(shí)候計(jì)數(shù)器加一。
var counter=0;
function demo3(){
console.log(counter+=1);
}
demo3();//1
demo3();//2
var counter=5;
demo3(); //6
上面的方法,如果在任何一個(gè)地方改變counter的值 計(jì)數(shù)器都會(huì)失效,javascript解決這種問題用到閉包,就是函數(shù)內(nèi)部?jī)?nèi)嵌函數(shù),再來看下利用閉包如何實(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
上面就是一個(gè)閉包使用的實(shí)例 ,函數(shù)add內(nèi)部?jī)?nèi)嵌一個(gè)plus函數(shù),count變量引用該返回的函數(shù),每次外部函數(shù)add執(zhí)行的時(shí)候都會(huì)開辟一塊內(nèi)存空間,外部函數(shù)的地址不同,都會(huì)重新創(chuàng)建一個(gè)新的地址,把plus函數(shù)嵌套在add函數(shù)內(nèi)部,這樣就產(chǎn)生了counter這個(gè)局部變量,內(nèi)次調(diào)用count函數(shù),該局部變量值加一,從而實(shí)現(xiàn)了真正的計(jì)數(shù)器問題。
1.4閉包的主要實(shí)現(xiàn)形式
這里主要通過兩種形式來學(xué)習(xí)閉包:
?、俸瘮?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的時(shí)候,25作為參數(shù)傳入foo中,這時(shí)判斷的x>num的num取值是創(chuàng)建函數(shù)的作用域中的num,即全局的num,而不是foo2內(nèi)部的num,因此打印出了25。
1.5閉包的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
?、俦Wo(hù)函數(shù)內(nèi)的變量安全 ,實(shí)現(xiàn)封裝,防止變量流入其他環(huán)境發(fā)生命名沖突
?、谠趦?nèi)存中維持一個(gè)變量,可以做緩存(但使用多了同時(shí)也是一項(xiàng)缺點(diǎn),消耗內(nèi)存)
?、勰涿詧?zhí)行函數(shù)可以減少內(nèi)存消耗
缺點(diǎn):
①其中一點(diǎn)上面已經(jīng)有體現(xiàn)了,就是被引用的私有變量不能被銷毀,增大了內(nèi)存消耗,造成內(nèi)存泄漏,解決方法是可以在使用完變量后手動(dòng)為它賦值為null;
?、谄浯斡捎陂]包涉及跨域訪問,所以會(huì)導(dǎo)致性能損失,我們可以通過把跨作用域變量存儲(chǔ)在局部變量中,然后直接訪問局部變量,來減輕對(duì)執(zhí)行速度的影響。
1.6閉包的使用
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log( i);
}, 1000);
}
console.log(i);
我們來看上面的問題,這是一道很常見的題,可這道題會(huì)輸出什么,一般人都知道輸出結(jié)果是 5,5,5,5,5,5,你仔細(xì)觀察可能會(huì)發(fā)現(xiàn)這道題還有很多巧妙之處,這6個(gè)5的輸出順序具體是怎樣的?5 -> 5,5,5,5,5 ,了解同步異步的人也不難理解這種情況,基于上面的問題,接下來思考如何實(shí)現(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
仔細(xì)查看setTimeout的api你會(huì)發(fā)現(xiàn)它還有第三個(gè)參數(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ù)表達(dá)式作為參數(shù)傳遞到for循環(huán)中,同樣實(shí)現(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塊級(jí)作用域的人會(huì)想到上面的方法。但是如果要實(shí)現(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 的定時(shí)器時(shí)間
})(i);
}
setTimeout(function() { // 這里增加定時(shí)器,超時(shí)設(shè)置為 5 秒
console.log(new Date, i);
}, 1000 * i);
//0 -> 1 -> 2 -> 3 -> 4 -> 5
還有下面的代碼,通過promise來實(shí)現(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,否則代碼不會(huì)按預(yù)期 work
}, 1000 * j); // 定時(shí)器的超時(shí)時(shí)間逐步增加
}));
})(i);
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000); // 注意這里只需要把超時(shí)設(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,實(shí)際上可以是任何異步操作
const sleep = (timeountMS) => new Promise((resolve) => {
setTimeout(resolve, timeountMS);
});
(async () => { // 聲明即執(zhí)行的 async 函數(shù)表達(dá)式
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
上面的代碼中都用到了閉包,總之,閉包找到的是同一地址中父級(jí)函數(shù)中對(duì)應(yīng)變量最終的值。
2.垃圾回收機(jī)制
JavaScript 中的內(nèi)存管理是自動(dòng)執(zhí)行的,而且是不可見的。我們創(chuàng)建基本類型、對(duì)象、函數(shù)……所有這些都需要內(nèi)存。
通常用采用的垃圾回收有兩種方法:標(biāo)記清除、引用計(jì)數(shù)。
1、標(biāo)記清除
垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記。然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標(biāo)記。
而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了。
最后。垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值,并回收他們所占用的內(nèi)存空間
2.引用計(jì)數(shù)
引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型賦值給該變量時(shí),則這個(gè)值的引用次數(shù)就是1。
相反,如果包含對(duì)這個(gè)值引用的變量又取得了另外一個(gè)值,則這個(gè)值的引用次數(shù)就減1。當(dāng)這個(gè)引用次數(shù)變成0時(shí),
則說明沒有辦法再訪問這個(gè)值了,因而就可以將其所占的內(nèi)存空間給收回來。這樣,垃圾收集器下次再運(yùn)行時(shí),
它就會(huì)釋放那些引用次數(shù)為0的值所占的內(nèi)存。
總結(jié)
以上就是本文的全部?jī)?nèi)容,希望給讀者帶來些許的幫助和進(jìn)步,方便的話點(diǎn)個(gè)關(guān)注,小白的成長(zhǎng)之路會(huì)持續(xù)更新一些工作中常見的問題和技術(shù)點(diǎn)。

粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
