首頁 >web前端 >js教程 >js深入理解閉包(附上程式碼)

js深入理解閉包(附上程式碼)

亚连
亚连原創
2018-05-19 14:13:561178瀏覽

這篇文章主要介紹了js的閉包,閉包算是js裡面比較不容易理解的點,現在整理出來分享給大家,有需要的可以了解一下。

閉包算是js裡面比較不容易理解的點,尤其是對沒有程式基礎的人來說。

其實閉包要注意的就那麼幾條,如果你都明白了那麼征服它並不是什麼難事兒。下面就讓我們來談談閉包的一些基本原理。

 閉包的概念

一個閉包就是一個函數和被創建的函數中的作用域物件的組合。 (作用域物件下面會說)

通俗一點的就是「 只要一個函數中嵌套了一個或多個函數,那麼我們就可以稱它們構成了閉包。」


##類似這樣:

function A() {
 var i = 5;
 return function() {
  console.log('i = '+i);
 }
}

var a = A();
a(); // i = 5

##1、外函數的局部變數若會被閉包函數呼叫就不會在外部函數執行完畢之後立即被回收。 我們知道,不管什麼語言,作業系統都會存在一個垃圾回收機制,將多餘分配的空間回收以便減少記憶體。而函數的生命週期的是從呼叫它開始的,當函數調用完畢的時候函數內部的局部變數等都會被回收機制回收。

我們拿上述例子來說,當我們的外部函數A調用完畢時,A中的局部變數i照理說就會被作業系統回收而不存在,但是當我們用了閉包結果就不是那樣了,i並不會被回收。試想,如果i被回收了那麼回傳的函數裡面豈不是就是印出undefined了?

i為什麼沒有被回收?

在javascript執行一個函數的時候都會創建一個作用域對象,將函數中的局部變數(函數的形參也是局部變數)保存進去,伴隨著那些傳入函數的變數一起被初始化。

所以當呼叫A的時候就創建了一個作用域對象,我們姑且稱之為Aa,那麼這個Aa應該是這樣的: Aa { i: 5; };  在A函數返回一個函數之後,A執行完畢。 Aa物件本來應該被回收,但由於傳回的函數使用了Aa的屬性i,所以傳回的函數儲存了一個指向Aa的引用,所以Aa不會被回收。

所以理解作用域對象,就能理解為什麼函數的局部變數在遇到閉包的時候不會在函數調用完畢時立即被回收了。

再來個例子:

function A(age) {
 var name = 'wind';
 var sayHello = function() {
  console.log('hello, '+name+', you are '+age+' years old!');
 };
 return sayHello;
}
var wind = A(20);
wind(); // hello, wind, you are 20 years old!

你能說出的它的作用域物件Ww是什麼嗎?

Ww{ age: 20; name: 'wind'; };

#2、每呼叫一次外部函數就產生一個新的閉包,以前的閉包依舊存在且互不影響。

3、同一個閉包會保留上一次的狀態,當它被再次呼叫時會在上一次的基礎上進行。

每呼叫一次外部函數產生的作用域物件都不一樣,你可以這樣想,上面的例子,你每次傳入的參數age不一樣,所以就每次產生的物件不一樣。

每呼叫一次外部函數那麼就會產生一個新的作用域物件。

function A() {
 var num = 42;
 return function() { console.log(num++); }
}
var a = A();
a(); // 42
a(); // 43

var b = A(); // 重新调用A(),形成新闭包
b(); // 42

這個程式碼讓我們發現了兩件事,一、當我們連續呼叫兩次a();,num會在原始基礎上自加。說明同一個閉包會保留上一次的狀態,當它被再次呼叫時會在上一次的基礎上進行。二、我們的b();的結果為42,表示它是新的閉包,並且不受其他閉包的影響。

我們可以這樣想,就好比我們吹肥皂泡一樣,我每次吹一下(調用外部函數),就會產生一個新的肥皂泡(閉包),多個肥皂泡可以同時存在且兩個肥皂泡之間不會互相影響。

4、在外部函數中存在的多個函數「 同生共死」

以下三個函數被同時宣告並且都可以對作用域物件的屬性(局部變數)進行訪問與操作。

var fun1, fun2, fun3;
function A() {
 var num = 42;
 fun1 = function() { console.log(num); }
 fun2 = function() { num++; }
 fun3 = function() { num--; } 
}

A();
fun1();  // 42
fun2(); 
fun2(); 
fun1();  // 44
fun3(); 
fun1();  //43

var old = fun1;

A(); 
fun1();  // 42
old();  // 43  上一个闭包的fun1()

由於函數不能有多個回傳值,所以我用了全域變數。我們再次可以看出在我們第二次呼叫A()時產生了一個新的閉包。

當閉包遇到循環變數

當我們說到閉包就不得不說當閉包遇到循環變數這一種情況,看如下程式碼:

######
function buildArr(arr) {
  var result = [];
  for (var i = 0; i < arr.length; i++) {
    var item = &#39;item&#39; + i;
    result.push( function() {console.log(item + &#39; &#39; + arr[i])} );
  }
  return result;
}

var fnlist = buildArr([1,2,3]);
fnlist[0](); // item2 undefined
fnlist[1](); // item2 undefined
fnlist[2](); // item2 undefined
#########怎麼會這樣呢?我們預想的三個輸出應該是 item0 1,  item1 2,  item2 3。為什麼結果卻是回傳的result陣列裡面儲存了三個 item2 undefined ? ###

原来当闭包遇到循环变量时都是循环结束之后统一保存变量值,拿我们上面的例子来说,i是循环变量,当循环全部结束的时候i正好是i++之后的3,而arr[3]是没有值的,所以为undefined,有人会疑惑:为什么item的值是item2,难道不应该是item3吗?注意,在最后一次循环的时候也就是i = 2的时候,item的值为item2,当i++,i = 3循环条件不满足循环结束,此时的item的值已经定下来了,所以此时的arr[i]为arr[3],而item为item2。这样能理解吗?如果我们将代码改成这样那就说得通了:

function buildArr(arr) {
  var result = [];
  for (var i = 0; i < arr.length; i++) { 
    result.push( function() {console.log(&#39;item&#39; + i + &#39; &#39; + arr[i])} );
  }
  return result;
}

var fnlist = buildArr([1,2,3]);
fnlist[1](); // item3 undefined

那么问题来了,如何改正呢?且看代码:

function buildArr(arr) {
  var result = [];
  for (var i = 0; i < arr.length; i++) {
    result.push( (function(n) {
      return function() {
       var item = &#39;item&#39; + n;
       console.log(item + &#39; &#39; + arr[n]);
      }
    })(i));
  }
  return result;
}

var fnlist = buildArr([1,2,3]);
fnlist[0](); // item0 1
fnlist[1](); // item1 2
fnlist[2](); // item2 3

我们可以用一个自执行函数将i绑定,这样i的每一个状态都会被存储,答案就和我们预期的一样了。

所以以后在使用闭包的时候遇到循环变量我们要习惯性的想到用自执行函数来绑定它。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

js 闭包常见的两种情况的解析

浅谈js 闭包引起的内存泄露问题_javascript技巧

让你分分钟学会 JS 闭包

以上是js深入理解閉包(附上程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn