對閉包的了解:
閉包是由函數以及建立該函數的詞法環境組成,形式上看就是函數内的子函數。
因為js有自動的垃圾回收機制,當變量不被引用時就會通過特殊的算法回收(一般是标記清除和引用計數兩種方法),當一個函數執行完後作用域會被銷毀,而閉包可以通路父級作用域,父級函數中的變量在閉包中被引用,那麼就不會被垃圾回收;是以閉包可以實作在函數外部通路内部的局部變量,并且可以使讓這些變量的值始終保持在記憶體中。
從記憶體來看閉包:
當JS引擎判斷形成閉包,就會在堆記憶體中開辟一塊空間,裡面有一個閉包對象closure,這個對象裡包含閉包用到的外部變量,之後要用這些變量的時候,就會去到堆裡面的closure對象裡面找。
了解練習:
例一:
var x = 10;
function fn(){
console.log(x);
}
function show(f){
var x = 20; //因為閉包是由函數及建立該函數的詞法環境組成,而fn建立的詞法環境中x=10
(function(){
f();
})()
}
show(fn); //10
例二:
function a() {
var i = 0;
function b() {
alert(++i);
}
return b;
}
var c = a();
c(); //1 i=0,第一次調用c 執行++i,輸出1
c(); //2 因為是閉包,i變量在b()中引用不會在c()執行完後釋放記憶體,是以再次c(),++i就是1+1=2
例三:
var add = function(x) {
var sum = 1;
var tmp = function(x) {
sum = sum + x;
return tmp;
}
tmp.toString = function() {
return sum;
}
return tmp;
}
alert(add(1)(2)(3)) // 6
第一次調用傳入了參數1,沒有使用,此時sum的值是1;然後傳回tmp函數
第二次調用tmp函數傳入 2,sum的值是1+2=3;
第三次重複調用tmp函數傳入3,sum的值是3+3=6;隐式自動調用tmp的toString()彈出6
例四:
var i = 0;
function outerFn(){
function innerFn(){
i++;
console.log(i);
}
return innerFn
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();//1
inner2();//1
inner1();//2
inner1();//3
inner2();//2
inner2();//3
因為閉包找到的是同一位址中父級函數中對應變量最終的值。
例五:
function fun(){
//1. 找受保護的變量: 外層函數的局部變量
var n=999;
//2. 外層函數共抛出哪些内層函數
nAdd=function(){n++};
return function(){ console.log(n) };
}
var get=fun();//fun的AO(n=999)
//var nAdd: function(){n++}
//get: function(){ console.log(n) };
get();//999
nAdd();//AO(n=1000)
get();//1000
因為nAdd沒有被定義過,強行指派會使JS将nAdd定義為全局變量,是以他也會被傳回到外部,故也将執行
例六:
for(var i = 0;i < 5;i++){
setTimeout(function (){
console.log(i++);
},4000)
}
console.log(i);
for循環并不是一個函數,是以沒有函數作用域,var聲明的變量也不存在塊級作用域,是以i就是個全局變量。
順序執行,先執行for,setTimeout異步宏任務放任務隊列等待執行
當for循環完i=5,執行console.log(i)輸出5
然後執行任務隊列的五個setTimeout宏任務,因為是閉包i的值儲存不被釋放,是以接下來輸出5,6,7,8,9
正常實作從0增加輸出:
1.立即執行函數,每一次都會建立函數執行環境得到對應的結果
for(var i = 0;i < 5;i++){
(function (x) {
setTimeout(function (){
console.log(x++);
},4000)
})(i);
}
2.setTimeout 的第三個參數
for ( var i=1; i<=5; i++) {
setTimeout( function timer(j) {
console.log( j );
}, i*1000, i);
}
3.使用 let 定義 i
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
記憶體溢出和記憶體洩漏:
記憶體溢出:當程式運作需要的記憶體超過了剩餘的記憶體時, 就出抛出記憶體溢出的錯誤
記憶體洩露:占用的記憶體沒有及時釋放
常見的記憶體洩漏:
1.沒有及時清理的計時器或回調函數
原因:定時器中有dom的引用,即使dom删除了,但是定時器還在,是以記憶體中還是有這個dom。
解決:手動删除定時器和dom。
2.意外的全局變量。
原因:全局變量,不會被回收。
解決:使用嚴格模式避免。
function fn () {
b = new Array[1000000]
a = [] //不小心沒有var定義,這時候a變量是全局的
}
fn()
function foo() {
this.variable = "potential accidental global";
}
// foo 調用自己,this 指向了全局對象(window)
foo();
3.閉包,函數執行完後, 函數内的局部變量沒有釋放, 占用記憶體時間會變長,容易造成記憶體洩露。
原因:閉包可以維持函數内局部變量,使其得不到釋放。
解決:将事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,删除對dom的引用。
4.dom清空或删除時,事件未清除導緻的記憶體洩漏。
原因:雖然别的地方删除了,但是對象中還存在對dom的引用
解決:手動删除。
5.子元素存在引用引起的記憶體洩漏
原因:div中的ul li 得到這個div,會間接引用某個得到的li,那麼此時因為div間接引用li,即使li被清空,也還是在記憶體中,并且隻要li不被删除,他的父元素都不會被删除。
解決:手動删除清空。
怎樣避免記憶體洩露:
1)減少不必要的全局變量,或者生命周期較長的對象,及時對無用的資料進行垃圾回收;
2)注意程式邏輯,避免“死循環”之類的 ;
3)避免建立過多的對象 原則:不用了的東西要及時歸還。
關于垃圾回收機制:
垃圾收集器會定期(周期性)找出那些不在繼續使用的變量,然後釋放其記憶體。
通常情況下有兩種實作方式:标記清除,引用計數。
标記清除:
工作原理:是當變量進入環境時,将這個變量标記為“進入環境”。當變量離開環境時,則将其标記為“離開環境”。标記“離開環境”的就回收記憶體。
工作流程:
1. 垃圾回收器,在運作的時候會給存儲在記憶體中的所有變量都加上标記。
2. 去掉環境中的變量以及被環境中的變量引用的變量的标記。
3. 再被加上标記的會被視為準備删除的變量。
4. 垃圾回收器完成記憶體清除工作,銷毀那些帶标記的值并回收他們所占用的記憶體空間。
引用計數:
工作原理:跟蹤記錄每個值被引用的次數。
工作流程:
1. 聲明了一個變量并将一個引用類型的值指派給這個變量,這個引用類型值的引用次數就是1。
2. 同一個值又被指派給另一個變量,這個引用類型值的引用次數加1.
3. 當包含這個引用類型值的變量又被指派成另一個值了,那麼這個引用類型值的引用次數減1.
4. 當引用次數變成0時,說明沒辦法通路這個值了。
5. 當垃圾收集器下一次運作時,它就會釋放引用次數是0的值所占的記憶體。