JavaScript閉包與作用域鏈實例分析

這篇文章主要介紹了JavaScript閉包與作用域鏈,結合實例形式分析了javascript閉包的具體概念、功能、並對比作用域鏈分析了閉包的相關使用技巧與注意事項,需要的朋友可以參考下

本文實例講述了JavaScript閉包與作用域鏈。分享給大家供大家參考,具體如下:

閉包定義

閉包指的是有權訪問另一個函數作用域中的變量的函數。創建閉包的常見方式,就是在一個函數A內部創建另一個函數B,那麼函數B就是一個閉包,可以訪問函數A作用域中的所有變量。

JavaScript的閉包與作用域鏈密不可分,因此本文可以和JavaScript的作用域鏈相對照分析,一定可以對JavaScript的閉包和作用域鏈有更深的理解。

下面我們仍然以createComparisonFunction爲例進行閉包的分析。

//step1: define createComparisonFunction
function createComparisonFunction(propertyName){
  return function(object1, object2){
    var value1 = object1[propertyName];
    var value2 = object2[propertyName];
    if (value1 < value2) {
      return -1;
    } else if (value1 > value2) {
      return 1;
    } else {
      return 0;
    }
  };
}
//step2: call createComparisonFunction
var compareName = createComparisonFunction("name");
var compareAge = createComparisonFunction("age");
//step3: call compare
var object1 = {
  name : "Nicholas",
  age : 25
};
var object2 = {
  name : "Greg",
  age : 27
};
var result1 = compareName(object1, object2); // 1
var result2 = compareAge(object1, object2); // -1
//step4: dereference closure for recycle memory
compareName = null;
compareAge = null;

在這個例子中,匿名函數function(object1, object2)是一個閉包,能訪問createComparisonFunction作用域裏的所有變量,自然也包含propertyName屬性, 因爲propertyName參數的不同,導致比較的屬性也有所不同,從而函數執行結果也有不同。

閉包與變量

JavaScript的作用域鏈中,我們瞭解到JavaScript是通過作用域鏈來確定函數執行環境的作用域的,這種機制會引出一個值得注意的副作用,即閉包只能取得包含函數中任何變量的最後一個值。閉包是通過引用外部函數的活動對象來訪問該活動對象中的所有變量,因此在外部函數執行過程中,這些變量的值可能會變化,但是在外部函數執行完畢之後,外部函數的活動對象便不會再改變,因此在執行閉包的時候,閉包通過作用域鏈訪問到外部函數的活動對象中的所有變量都只可能是在外部函數執行完畢之後,外部函數的活動對象中最後所保存的值。我們通過一個例子來說明這種副作用。

function createFunctions(){
  var result = new Array();
  for (var i = 0; i < 10; i++){
    result[i] = function(){
      return i;
    };
  }
  return result;
}
var functions = createFunctions();
for(var i = 0; i < functions.length; i++){
  console.log(functions[i]());
}

輸出的結果是

10 10 10 10 10 10 10 10 10 10

從表面上看,似乎每個函數都應該返回自己的索引值,但實際上,每個函數都返回10。因爲每個函數的作用域鏈中都保存着createFunctions函數的活動對象,所以他們引用的都是這個createFunctions函數的活動對象中的變量i,在createFunctions函數返回之後,變量i的值是10,此時每個函數都引用着保存變量i的同一個變量對象,所以每個函數內部i的值都是10。

我們以調用functions[3]()爲例,圖解一下:

Closure函數的Function對象的作用域鏈引用的createFunctions的活動對象中保留的變量i的值爲10。所以不管是functions[3]()還是functions[5](),其運行時上下文的作用域鏈引用的createFunctions的活動對象都是同一個活動對象,該活動對象中保留的變量i的值是10。

如何避免這種局面?我們可以通過創建另一個匿名函數讓閉包的行爲符合預期。

function createFunctions(){
  var result = new Array();
  for (var i = 0; i < 10; i++){
    result[i] = function(num){
      return function(){
        return num;
      }
    }(i);
  }
  return result;
}
var functions = createFunctions();
for(var i = 0; i < functions.length; i++){
  console.log(functions[i]());
}

輸出的結果是

0 1 2 3 4 5 6 7 8 9

這個代碼片段與前面的代碼的區別在於立即調用了一個匿名函數function(num),使得閉包function()引用的是function(num)的活動對象,訪問的是該活動對象中的變量num而不是createFunctions活動對象中的變量i,而在立即調用function(num)的num是索引值0,1,2…9。

我們仍舊以調用functions[3]()爲例,圖解一下:

在執行createFunctions函數的時候,會依次調用function(0), function(1) … function(9), 生成function(0), function(1) … function(9)這10個function(num)的活動對象,而result[0], result[1] … result[9]這10個匿名函數對象的作用域鏈分別引用這10個function(num)的活動對象,而其中的變量num的值也對應的爲0, 1 … 9。

所以不管是functions[3]()還是functions[5](),其運行時上下文的作用域鏈都會引用在執行createFunctions函數時候所執行的function(3)或者function(5)這些function(num)函數的活動對象,這些活動對象都是不同的活動對象,其中保留的num值分別爲3, 5。

更多關於JavaScript相關內容感興趣的讀者可查看本站專題:《javascript面向對象入門教程》、《JavaScript錯誤與調試技巧總結》、《JavaScript數據結構與算法技巧總結》、《JavaScript遍歷算法與技巧總結》及《JavaScript數學運算用法總結

希望本文所述對大家JavaScript程序設計有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章