閉包就是有權限訪問 其他函數作用域的局部變量的 一個函數
在JS中,變量的作用域屬於函數作用域,在函數執行后作用域就會被清理、內存也隨之被收回,但是由於閉包時建立在一個函數內部的子函數,由於其可訪問上級作用域的原因,即使上級函數執行完,作用域也不會隨之銷毀,這時的子函數---也就是閉包,便擁有了訪問上級作用域中的變量的權限,即使上級函數執行完后,作用域內的值也不會被銷毀。
function a(){
var i=0; function b(){ alert(++i); } return b; } var c=a(); c(); // 1 c(); // 2 c(); // 3
function fn3(){
var a = 10; return function(){ a--; console.log(a); } } fn3()();//9 fn3()();//9
function fn3(){
var a = 10; return function(){ a--; console.log(a); } } var val = fn3(); val(); //9 val(); //8
function Person(){
var a = 10; return { age:function(){ a += 1; console.log(a); } } } var result = Person(); result.age(); // 11 result.age(); // 12
function add(x) {
return function(y) { return x + y } } var add5 = add(5); var add10 = add(10); console.log(add5(2)); console.log(add10(2));
由於閉包可以緩存上級作用域,那么就使得函數外部打破了“函數作用域”的束縛,可以訪問函數內部的變量。
以平時使用的 AJAX 成功回調為例,這里其實就是個閉包,由於上述的特性,回調就擁有了整個上級作用域的訪問和操作能力,提高了極大的便利。開發者不用去學鈎子函數來操作上級函數作用域內部的變量了
閉包隨處可見,一個 Ajax 請求的成功回調,一個事件綁定的回調方法,一個 setTimeout 的延時回調,或者一個函數內部返回另一個匿名函數,這些都是閉包。
簡而言之,無論使用何種方式對函數類型的值進行傳遞,當函數在別處被調用時,都有閉包的身影
如果一個對象不再被引用, 那么這個對象就會被垃圾回收機制回收;
如果兩個對象互相引用, 且不再被第3者所引用, 那么這兩個互相引用的對象也會被回收。
(在閉包中,父函數被子函數引用,子函數又被外部的一個變量引用,這就是父函數不被回收的原因)
function foo(){
var name = 'edward'; //被標記"進入環境"
var age = "25"; //被標記"進入環境"
} foo(); //執行完畢后之后,name和age又被標記"離開環境",被回收
垃圾回收機制在運行的時候會給存儲在內存中的所有變量都加上標記(可以是任何標記方式),然后,它會去掉處在環境中的變量及被環境中的變量引用的變量標記(閉包)。而在此之后剩下的帶有標記的變量被視為准備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后垃圾回收機制到下一個周期運行時,將釋放這些變量的內存,回收它們所占用的空間。
語言引擎有一張"引用表",保存了內存里面所有資源(通常是各種值)的引用次數。如果一個值的引用次數是0,就表示這個值不再用到了,因此可以將這塊內存釋放。
const arr = [1,2,3,4];
console.log("welcome to my blog");
上面的代碼中,數組[1,2,3,4]是一個值,會占用內存。變量arr是僅有的對這個值的引用,因此引用次數為1。盡管后面的代碼沒有用到arr,它是會持續占用內存。
如果增加一行代碼,解除arr對[1,2,3,4]引用,這塊內存就可以被垃圾回收機制釋放了。
const arr = [1,2,3,4];
console.log("welcome to my blog"); arr = null;
因此,並不是說有了垃圾回收機制,程序員就輕松了。你還是需要關注內存占用:那些很占空間的值,一旦不再用到,你必須檢查是否還存在對它們的引用。如果是的話,就必須手動解除引用。
【思考】:
什么情況會引起內存泄漏?
雖然有垃圾回收機制但是我們編寫代碼操作不當還是會造成內存泄漏。
1. 意外的全局變量引起的內存泄漏。
原因:全局變量,不會被回收。
解決:使用嚴格模式避免。
2. 閉包引起的內存泄漏
原因:閉包可以維持函數內局部變量,使其得不到釋放。
解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。
3. 沒有清理的DOM元素引用
原因:雖然別的地方刪除了,但是對象中還存在對dom的引用
解決:手動刪除。
4. 被遺忘的定時器或者回調
原因:定時器中有dom的引用,即使dom刪除了,但是定時器還在,所以內存中還是有這個dom。
解決:手動刪除定時器和dom。
5. 子元素存在引用引起的內存泄漏
原因:div中的ul li 得到這個div,會間接引用某個得到的li,那么此時因為div間接引用li,即使li被清空,也還是在內存中,並且只要li不被刪除,他的父元素都不會被刪除。
解決:手動刪除清空。
什么放在內存中?什么不放在內存中?
基本類型是:Undefined/Null/Boolean/Number/String
基本類型的值存在內存中,被保存在棧內存中。從一個變量向另一個變量復制基本類型的值,會創建這個值的一個副本。
引用類型:object
引用類型的值是對象,保存在堆內存中。
1. 包含引用類型值的變量實際上包含的並不是對象本身,而是一個指向該對象的指針。從一個變量向另一個變量復制引用類型的值,復制的其實是指針,因此兩個變量最終都指向同一個對象。
2. js不允許直接訪問內存中的位置,也就是不能直接訪問操作對象的內存空間。在操作對象時,實際上是在操作對象的引用而不是實際的對象。
棧和堆的區別
一、堆棧空間分配區別:
1、棧(操作系統):由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧;
2、堆(操作系統): 一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似於鏈表。
二、堆棧緩存方式區別:
1、棧使用的是一級緩存, 他們通常都是被調用時處於存儲空間中,調用完畢立即釋放;
2、堆是存放在二級緩存中,生命周期由虛擬機的垃圾回收算法來決定(並不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。
三、堆棧數據結構區別:
堆(數據結構):堆可以被看成是一棵樹,如:堆排序;
棧(數據結構):一種先進后出的數據結構。
【小練習】:請說出下列的打印結果
1.
function foo() {
var a = 1
function fn() { a++; console.log(a) } return fn() } console.log(foo); foo(); foo();
2.
function fn1() {
var a = 1; var b = 2; function fn() { a++; console.log(a) } return fn; } var x = fn1(); x(); x(); var x = fn1(); x();