前言
在我們了解了執(zhí)行上下文EC之后荣瑟,我們應(yīng)該知道了扫皱,js執(zhí)行的正常流程足绅。但是我們在工作、面試時經(jīng)常會遇到閉包這個奇怪的東西韩脑,今天我們就來詳細的了解一下氢妈。閉包是如何形成的,閉包在ECS中是如何執(zhí)行的段多。
正文
作用域和作用域鏈
講閉包之前我們需要先了解作用域和作用域鏈首量。
作用域: 表示一個變量的可用范圍,從而避免不同范圍的變量之間相互干擾。
作用域分為兩種:
- 全局作用域:在瀏覽器端全局作用域就是window加缘,他可以重復(fù)使用鸭叙,隨處可用。但是全局作用的定義的變量容易被污染
- 函數(shù)作用域:函數(shù)聲明的時候會自動創(chuàng)建函數(shù)作用域?qū)ο髎cope,他指向函數(shù)聲明時的作用域拣宏。而每個函數(shù)調(diào)用時創(chuàng)建的AO對象前會先創(chuàng)建當前函數(shù)的作用域沈贝,并創(chuàng)建parent對象通過函數(shù)聲明時的作用域?qū)ο髎cope指向他函數(shù)的父級作用域。他不會污染全局勋乾,但是不會重復(fù)使用缀程。函數(shù)執(zhí)行完畢后會隨著函數(shù)執(zhí)行上問出棧=>AO對象銷毀而銷毀
作用域鏈
變量取值到創(chuàng)建這個變量的函數(shù)的作用域中取值。但是如果在當前作用域中沒有查到值市俊,就會向通過parent對象去父作用域去查杨凑,直到查到全局作用域,這么一個查找過程形成的鏈條就叫做作用域鏈摆昧。
閉包
為什么會使用閉包撩满?
我們先總結(jié)下全局變量和局部變量的優(yōu)缺點。
- 全局變量:可以重用绅你、但是會造成全局污染而且容易被篡改
- 局部變量:僅函數(shù)內(nèi)使用不會造成全局污染也不會被篡改伺帘、不可以重用
從上面可以看出全局變量和局部變量的優(yōu)缺點剛好是相對的。閉包的出現(xiàn)正好結(jié)合了全局變量和局部變量的優(yōu)點忌锯。
閉包是怎么形成伪嫁,在ECS如何執(zhí)行的呢?
我們以一下代碼為例:
function add(){
var n = 0
return function(){
n++
console.log(n)
}
}
const num = add()
num()
num()
num()
代碼開始執(zhí)行時
首先創(chuàng)建全局執(zhí)行上下文偶垮,并將全局執(zhí)行上下文壓入執(zhí)行上下文棧底张咳。
那么全局執(zhí)行上下文活動對象AO=>window對象中會有以下內(nèi)容
執(zhí)行順序:
- 全局函數(shù)add => 內(nèi)存地址 => add(){....}
- 全局變量num => undefined
函數(shù)在聲明時會自動創(chuàng)建一個scope對象指向創(chuàng)建時的作用域(AO活動對象),此時作用域是window似舵,所以聲明的add函數(shù)的socpe指向window
add()執(zhí)行
執(zhí)行順序:
- add執(zhí)行上下文入棧脚猾,創(chuàng)建add活動對象AO
- add函數(shù)AO對象中有:
- 局部變量n => 0
- parent => window對象(parent根據(jù)add函數(shù)聲明時scope)
- add執(zhí)行返回一個函數(shù),函數(shù)聲明創(chuàng)建scope對象指向當前作用域 => add活動對象AO
- 返回的函數(shù)砚哗,函數(shù)被全局變量num引用
add()執(zhí)行完成龙助,出棧
add()執(zhí)行完成。add的執(zhí)行上下文出棧蛛芥。
此時問題來了提鸟。
add()執(zhí)行上下文已經(jīng)出棧了。但是add的活動對象AO沒有被釋放仅淑!
為什么呢? 根據(jù)圖片的我標注出來的紅線称勋,這里形成了一個循環(huán)引用。所有add的活動對象AO無法被釋放漓糙。這樣就形成了一個閉包铣缠。
num()執(zhí)行
執(zhí)行順序:
- num執(zhí)行上下文入棧。創(chuàng)建num活動對象AO
- num函數(shù)AO對象中:
- parent => add函數(shù)AO對象(parent根據(jù)num函數(shù)聲明時scope)
- 執(zhí)行n++,發(fā)現(xiàn)當前函數(shù)作用域中沒有n變量蝗蛙,則根據(jù)作用域鏈向上查找蝇庭。找到add的AO中有n變量。n++
num()執(zhí)行完成捡硅,出棧
num()執(zhí)行完成哮内。num的執(zhí)行上下文出棧。num的活動對象AO被垃圾回收壮韭。
接下來的num()執(zhí)行北发,和上述步驟相同。
到這里閉包函數(shù)是如何執(zhí)行也就解釋清楚了喷屋。
閉包的缺點
通過上面的分析琳拨。我們發(fā)現(xiàn)沒生成一個閉包就會有一個活動對象AO常駐內(nèi)存,不會被銷毀屯曹,所以有可能會導(dǎo)致內(nèi)存溢出狱庇。
如何取消閉包
通過上面的分析。我們發(fā)現(xiàn)生成閉包就是因為有一個循環(huán)引用恶耽。導(dǎo)致活動對象AO無法被銷毀垃圾回收密任。所以我們?nèi)绻胍∠]包,只需要打斷循環(huán)引用就好了: num = null偷俭。