首頁  >  文章  >  web前端  >  JavaScript閉包-閉包中的變數與this對象

JavaScript閉包-閉包中的變數與this對象

黄舟
黄舟原創
2017-01-20 14:19:351075瀏覽

在JavaScript中作用域鏈的機制會引發一些副作用:閉包只能夠取得包含函數中任何變數的最後一個值。在使用閉包的時候,我們一定要注意變數值的問題,因為這是經常出錯的地方。

下面我們以一個非常極端的例子來說明這個問題,在實際開發中我們一般不會這樣寫程式碼。這個範例的程式碼如下:

function fn1(){
  var arr = new Array();
  //变量i保存在fn1作用域中
  for(var i = 0; i < 10;i++){
    arr[i] = function(){
      return i;
    }
  }
  return arr;
}
 
var values = fn1();
for(var i = 0; i < values.length;i++){
  //此时通过闭包来调用所有的函数,当输出i的时候会到上一级的作用域中查找,此时i的值是10,所以输出的都是10
  document.write(values[i]()+"<br>");
}

執行上面的程式碼,我們預期會在頁面中列印出0-9,但是實際上會列印10個10。讓我們來分析這段程式碼:實現程式碼中創建了一個函數fn1,在函數中創建了一個數組對象,並透過一個for循環為數組賦值,循環了10次,每一次往數組中填充一個匿名函數返回值,最後傳回數組物件。接著取得fn1函數的引用,然後透過循環在頁面中輸出數組中的值。

上面的程式的作用域鏈記憶體模型如下圖所示:

JavaScript閉包-閉包中的變數與this對象

從圖中我們可以看到,每次在fn1函數中的循環都會產生一個匿名函數,它們有各自的作用域鏈,它們的作用域鏈的高位都指向全域作用域,中間位指向外層的fn1作用域,低位才是指向自己的作用域。

當函數fn1執行完成之後,fn1作用域中的屬性i的值為10,此時GC開始回收fn1,但它發現有匿名函數指向fn1的作用域,所以fn1的作用域不會被回收。

在匿名函數執行的時候,它在自己的空間中查找屬性i,但是沒有找到,於是就到它上級的fn1作用域中去查找,此時,fn1作用域中的i值為10,所以所以匿名函數都會得到相同的i值:10。

解決這個問題的方法是在匿名函數中再傳回一個匿名函數,並且透過一個變數來保存目前的數值。程式碼如下:

function fn1(){
  var arr = new Array();
   
  for(var i = 0; i < 10;i++){
    arr[i] = function(num){
      return function(){
        return num;
      }
    }(i);
     
  }
  return arr;
}
 
var values = fn1();
for(var i = 0; i < values.length;i++){
  //每一个fs都是在不同的作用域链中,num也是保存在不同的作用域中,所以输出0-9
  document.write(values[i]()+"<br>");
}

此時,num的值保存在每個匿名函數自己的作用域中,數值剛好等於每次迴圈的索引值。這樣,在每次呼叫匿名函數的時候,它會在自己的空間中找到num屬性,而這些num的值都是不同的,同時也不會再到fn1函數作用域中去尋找i屬性。

上面的程式碼會產生20個匿名函數的作用域,如果程式碼中不是簡單的回傳值,而是一些更複雜的操作,將會佔用大量的記憶體空間。

 閉包中的this物件

在閉包中使用this物件也會出現一些意想不到的問題。 this物件是在執行時期基於函數的執行環境綁定的:對於全域函數,this物件就是window,而當函數在作為某個物件的方法被呼叫時,this就是那個物件。在匿名函數中,this物件通常是指向window的。我們來看下面的例子:

var name = "window";
var person = {
  name : "Leon",
  age:22,
  say:function(){
    return function(){
      return this.name;
    }
  }
}
console.info(person.say()()); //控制台输出:window

上面的程式碼中,我們呼叫person物件的say()方法時,印出來的不是person物件的名字,而是全域的名字「window」。當完成person.say()之後,函數調用完畢,在該函數呼叫結束之前,this是在指向person的,但是在調用匿名函數的時候,this就指向window了,所以得到的結果是“window” 。

解決這個問題的方法是在say()方法中將this引用賦值給一個臨時變數。程式碼如下:

var name = "window";
var person = {
  name : "Leon",
  age:22,
  say:function(){
    var that = this;
    return function(){
      return that.name;
    }
  }
}
console.info(person.say()()); //控制台输出:Leon

以上就是JavaScript閉包-閉包中的變數和this物件的內容,更多相關內容請關注PHP中文網(www.php.cn)!


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