JS 閉包(內存溢出與內存泄漏)(垃圾回收機制)

1.有關閉包定義javascript

閉包是指有權訪問另外一個函數做用域中變量的函數,建立閉包的最多見的
方式就是在一個函數內建立另外一個函數,經過另外一個函數訪問這個函數的局部變量

閉包的特性:
    函數內再嵌套函數
    內部函數能夠引用外層的參數和變量
    參數和變量不會被垃圾回收機制回收

說說你對閉包的理解html

使用閉包主要是爲了設計私有的方法和變量。閉包的優勢是能夠避免全局變量的污染,
缺點是閉包會常駐內存,會增大內存使用量,使用不當很容易形成內存泄露。在js中,
函數即閉包,只有函數纔會產生做用域的概念

閉包 的最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些
變量始終保持在內存中

閉包的另外一個用處,是封裝對象的私有屬性和私有方法

好處:可以實現封裝和緩存等;

壞處:就是消耗內存、不正當使用會形成內存溢出的問題

使用閉包的注意點java

因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用
閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露

解決方法是,在退出函數以前,將不使用的局部變量所有刪除

閉包的定義其實很簡單:函數 A 內部有一個函數 B,函數 B 能夠訪問到函數 A 中的變量,那麼函數 B 就是閉包

function A() {
  let a = 1
  window.B = function () {
      console.log(a)
  }
}
A()
B() // 1

閉包會產生一個很經典的問題:面試

多個子函數的[[scope]]都是同時指向父級,是徹底共享的。所以當父級的
變量對象被修改時,全部子函數都受到影響。

解決:數組

變量能夠經過 函數參數的形式 傳入,避免使用默認的[[scope]]向上查找
使用setTimeout包裹,經過第三個參數傳入
使用 塊級做用域,讓變量成爲本身上下文的屬性,避免共享

2.閉包簡單例子
指的是有權訪問另外一個函數做用域中變量的函數,
建立閉包的常見方式,就是在一個函數內部建立另外一個函數。瀏覽器

 function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }
function f1(){
    var n=999;
    function f2(){
      alert(n); 
    }
    return f2;
  }
  var result=f1();
  result(); // 999

3.閉包的用處:緩存

閉包能夠用在許多地方。它的最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。閉包

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

4.使用必閉包的問題:異步

因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題。函數

閉包的例子:

function outerFun()
{
 var a=0;
 function innerFun()
 {
  a++;
  alert(a);
 }
 return innerFun;  //注意這裏
}
var obj=outerFun();
obj();  //結果爲1
obj();  //結果爲2
var obj2=outerFun();
obj2();  //結果爲1
obj2();  //結果爲2
function outerFun()
{
 //沒有var
 a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);
結果爲 0,0 真是奇怪,爲何呢?

做用域鏈是描述一種路徑的術語,沿着該路徑能夠肯定變量的值 .當執行a=0時,因
爲沒有使用var關鍵字,所以賦值操做會沿着做用域鏈到var a=4;  並改變其值.

5.閉包內的微觀世界
參考學習:https://www.cnblogs.com/goloving/p/7062212.html
  若是要更加深刻的瞭解閉包以及函數a和嵌套函數b的關係,咱們須要引入另外幾個概念:函數的執行環境(excution context)、活動對象(call object)、做用域(scope)、做用域鏈(scope chain)。以函數a從定義到執行的過程爲例闡述這幾個概念。

1.當定義函數a的時候,js解釋器會將函數a的做用域鏈(scope chain)設置爲定義a時a所在的「環境」,若是a是一個全局函數,則scope chain中只有window對象。
當執行函數a的時候,a會進入相應的執行環境(excution context)。
2.在建立執行環境的過程當中,首先會爲a添加一個scope屬性,即a的做用域,其值就爲第1步中的scope chain。即a.scope=a的做用域鏈。
3.而後執行環境會建立一個活動對象(call object)。活動對象也是一個擁有屬性的對象,但它不具備原型並且不能經過Javascript代碼直接訪問。建立完活動對象後,把活動對象添加到a的做用域鏈的最頂端。此時a的做用域鏈包含了兩個對象:a的活動對象和window對象。
4.下一步是在活動對象上添加一個arguments屬性,它保存着調用函數a時所傳遞的參數。
5.最後把全部函數a的形參和內部的函數b的引用也添加到a的活動對象上。在這一步中,完成了函數b的的定義,所以如同第3步,函數b的做用域鏈被設置爲b所被定義的環境,即a的做用域。

當在函數b中訪問一個變量的時候,搜索順序是:

先搜索自身的活動對象,若是存在則返回,若是不存在將繼續搜索函數a的活動對象,
依次查找,直到找到爲止。
若是函數b存在prototype原型對象,則在查找完自身的活動對象後先查找自身的原型
對象,再繼續查找。這就是Javascript中的變量查找機制。
若是整個做用域鏈上都沒法找到,則返回undefined。

函數的定義與執行。文中提到函數的做用域是在定義函數時候就已經肯定,而不是在執行的時候肯定

6.有關閉包經典案例
經典面試題,循環中使用閉包解決 var 定義函數的問題

for ( var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

首先由於 setTimeout 是個異步函數,全部會先把循環所有執行完畢,這時候 i 就是 6 了,因此會輸出一堆 6。
解決辦法兩種,第一種使用閉包

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}

第二種就是使用 setTimeout 的第三個參數

for ( var i=1; i<=5; i++) {
	setTimeout( function timer(j) {
		console.log( j );
	}, i*1000, i);
}

第三種就是使用 let 定義 i 了

for ( let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

有關內存溢出與內存泄漏

1. 內存溢出
  * 一種程序運行出現的錯誤
  * 當程序運行須要的內存超過了剩餘的內存時, 就出拋出內存溢出的錯誤
2. 內存泄露
  * 佔用的內存沒有及時釋放
  * 內存泄露積累多了就容易致使內存溢出
  * 常見的內存泄露:
    * 意外的全局變量
    * 沒有及時清理的計時器或回調函數
    * 閉包
// 1. 內存溢出
  var obj = {}
  for (var i = 0; i < 10000; i++) {
    obj[i] = new Array(10000000)
    console.log('-----')
  }

  // 2. 內存泄露
    // 意外的全局變量
  function fn() {
    a = new Array(10000000)
    console.log(a)
  }
  fn()

   // 沒有及時清理的計時器或回調函數
  var intervalId = setInterval(function () { //啓動循環定時器後不清理
    console.log('----')
  }, 1000)

  // clearInterval(intervalId)

    // 閉包
  function fn1() {
    var a = 4
    function fn2() {
      console.log(++a)
    }
    return fn2
  }
  var f = fn1()
  f()

  // f = null

7.js垃圾回收機制
轉載:https://www.cnblogs.com/zhwl/p/4664604.html
因爲字符串、對象和數組沒有固定大小,當他們的大小已知時,才能對他們進行動態的存儲分配。JavaScript程序每次建立字符串、數組或對象時,解釋器都必須分配內存來存儲那個實體。只要像這樣動態地分配了內存,最終都要釋放這些內存以便他們可以被再用,不然,JavaScript的解釋器將會消耗完系統中全部可用的內存,形成系統崩潰。

如今各大瀏覽器一般用採用的垃圾回收有兩種方法:標記清除、引用計數
標記清除
這是javascript中最經常使用的垃圾回收方式。當變量進入執行環境是,就標記這個變量爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到他們。當變量離開環境時,則將其標記爲「離開環境」。
引用計數  另外一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數就減1。當這個引用次數變成0時,則說明沒有辦法再訪問這個值了,於是就能夠將其所佔的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數爲0的值所佔的內存。

相關文章
相關標籤/搜索