欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

全面理解JavaScript中的閉包

 更新時間:2016年05月12日 18:29:24   作者:LuckyJing  
將外部作用域中的局部變量封閉起來的函數(shù)對象稱為閉包(Closure),被封閉起來的變量與封閉它的函數(shù)對象有相同的生命周期,這在JavaScript中比較難理解而且十分重要,接下來帶大家一起來全面理解JavaScript中的閉包:

引子

閉包是有權訪問另一個函數(shù)作用域中的變量的函數(shù)。
閉包是javascript中很難理解的部分,很多高級的應用都依靠閉包來實現(xiàn)的,我們先來看下面的一個例子:

function outer() {
  var i = 100;
  function inner() {
    console.log(i);
  }
}

上面代碼,根據(jù)變量的作用域,函數(shù)outer中所有的局部變量,對函數(shù)inner都是可見的;函數(shù)inner中的局部變量,在函數(shù)inner外是不可見的,所以在函數(shù)inner外是無法讀取函數(shù)inner的局部變量的。

既然函數(shù)inner可以讀取函數(shù)outer的局部變量,那么只要將inner作為返會值,就可以直接在ouer外部讀取inner的局部變量。

function outer() {
  var i = 100;
  function inner() {
     console.log(i);
  }
  return inner;
}
var rs = outer();
rs();

這個函數(shù)有兩個特點:

  • 函數(shù)inner嵌套在函數(shù)ouer內部;
  • 函數(shù)outer返回函數(shù)inner。

這樣執(zhí)行完var rs = outer()后,實際rs指向了函數(shù)inner。這段代碼其實就是一個閉包。也就是說當函數(shù)outer內的函數(shù)inner被函數(shù)outer外的一個變量引用的時候,就創(chuàng)建了一個閉包。

作用域
簡單的說,作用域就是變量與函數(shù)的可訪問范圍,即作用域控制著變量與函數(shù)的可見性和生命周期。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。

全局作用域

var num1 = 1;
function fun1 (){
  num2 = 2;
}

以上三個對象num1,num2和fun1均是全局作用域,這里要注意的是末定義直接賦值的變量自動聲明為擁有全局作用域;

局部作用域

function wrap(){
  var obj = "我被wrap包裹起來了,wrap外部無法直接訪問到我";
  function innerFun(){
    //外部無法訪問我
  }
}

作用域鏈
Javascript中一切皆對象,這些對象有一個[[Scope]]屬性,該屬性包含了函數(shù)被創(chuàng)建的作用域中對象的集合,這個集合被稱為函數(shù)的作用域鏈(Scope Chain),它決定了哪些數(shù)據(jù)能被函數(shù)訪問。

function add(a,b){
  return a+b;
}

當函數(shù)創(chuàng)建的時候,它的[[scope]]屬性自動添加好全局作用域
2016512181604658.png (824×236)

var sum = add(3,4);

當函數(shù)調用的時候,會創(chuàng)建一個稱為運行期上下文(execution context)的內部對象,z這個對象定義了函數(shù)執(zhí)行時的環(huán)境。它也有自己的作用域鏈,用于標識符解析,而它的作用域鏈初始化為當前運行函數(shù)的[[Scope]]所包含的對象。

2016512181652539.png (835×400)

在函數(shù)執(zhí)行過程中,每遇到一個變量,都會經歷一次標識符解析過程以決定從哪里獲取和存儲數(shù)據(jù)。該過程從作用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,如果找到了就使用這個標識符對應的變量,如果沒找到繼續(xù)搜索作用域鏈中的下一個對象,如果搜索完所有對象(最后一個為全局對象)都未找到,則認為該標識符未定義。

閉包
閉包簡單來說就是一個函數(shù)訪問了它的外部變量。

var quo = function(status){
  return {
    getStatus: function(){
      return status;
    }
  }
}

status保存在quo中,它返回了一個對象,這個對象里的方法getStatus引用了這個status變量,即getStatus函數(shù)訪問它的外部變量status;

var newValue = quo('string');//返回了一個匿名對象,被newValue引用著
newValue.getStatus();//訪問到了quo的內部變量status

假如并沒有getStatus這個方法,那么quo('sting')結束后,status自動被回收,正是因為返回的匿名對象被一個全局對象引用,那么這個匿名對象又依賴于status,所以會阻止status的釋放。

例子:

//錯誤方案
var test = function(nodes){
  var i ;
  for(i = 0;i<nodes.length;i++){
    nodes[i].onclick = function(e){
      alert(i);
    }
  }
}

匿名函數(shù)創(chuàng)建了一個閉包,那么其訪問的i是外部test函數(shù)中的i,所以每一個節(jié)點實際上引用的是同一個i。

2016512181810708.png (346×302)

//改進方案
var test = function(nodes){
  var i ;
  for(i = 0;i<nodes.length;i++){
    nodes[i].onclick = function(i){
      return function(){
        alert(i);
      };
    }(i);
  }
}

每一個節(jié)點綁定了一個事件,這個事件接收一個參數(shù),并且立即運行,傳入i,因為是按值傳遞的,所以每一次循環(huán)都會為當前i產生一個新的備份。

2016512181833005.png (358×306)

閉包的作用

function outer() {
  var i = 100;
  function inner() {
     console.log(i++);
  }
  return inner;
}
var rs = outer();
rs();  //100
rs();  //101
rs();  //102

上面的代碼中,rs是閉包inner函數(shù)。rs共運行了三次,第一次100,第二次101,第三次102,這說明在函數(shù)outer中的局部變量i一直保存在內存中,并沒有在調用自動清除。

閉包的作用就是在outer執(zhí)行完畢并返回后,閉包使javascript的垃圾回收機制(grabage collection)不會回收outer所占的內存,因為outer的內部函數(shù)inner的執(zhí)行要依賴outer中的變量。(另一種解釋:outer是inner的父函數(shù),inner被賦給了一個全局變量,導致inner會一直在內存中,而inner的存在依賴于outer,因些outer也始終于在內存中,不會在調用結束后被垃圾收集回收)。

閉包有權訪問函數(shù)內部的所有變量。
當函數(shù)返回一個閉包時,這個函數(shù)的作用域將會一直在內存中保存到閉包不存在為止。

閉包與變量

由于作用域鏈的機制,閉包只能取得包含函數(shù)中任何變量的最后一個值??聪旅胬樱?/p>

function f() {
  var rs = [];

  for (var i=0; i <10; i++) {
    rs[i] = function() {
      return i;
    };
  }

  return rs;
}

var fn = f();

for (var i = 0; i < fn.length; i++) {
  console.log('函數(shù)fn[' + i + ']()返回值:' + fn[i]());
}

函數(shù)會返回一個數(shù)組,表面上看,似乎每個函數(shù)都應該返回自己的索引值,實際上,每個函數(shù)都返回10,這是因為第個函數(shù)的作用域鏈上都保存著函數(shù)f的活動對象,它們引用的都是同一變量i。當函數(shù)f返回后,變量i的值為10,此時每個函數(shù)都保存著變量i的同一個變量對象。我們可以通過創(chuàng)建另一個匿名函數(shù)來強制讓閉包的行為符合預期。

function f() {
  var rs = [];

  for (var i=0; i <10; i++) {
    rs[i] = function(num) {
      return function() {
        return num;
      };
    }(i);
  }

  return rs;
}

var fn = f();

for (var i = 0; i < fn.length; i++) {
  console.log('函數(shù)fn[' + i + ']()返回值:' + fn[i]());
}

這個版本中,我們沒有直接將閉包賦值給數(shù)組,而是定義了一個匿名函數(shù),并將立即執(zhí)行匿名函數(shù)的結果賦值給數(shù)組。這里匿名函數(shù)有一個參數(shù)num,在調用每個函數(shù)時,我們傳入變量i,由于參數(shù)是按值傳遞的,所以就會將變量i復制給參數(shù)num。而在這個匿名函數(shù)內部,又創(chuàng)建了并返回了一個訪問num的閉包,這樣,rs數(shù)組中每個函數(shù)都有自己num變量的一個副本,因此就可以返回不同的數(shù)值了。

閉包中的this對象

var name = 'Jack';

var o = {
  name : 'bingdian',

  getName : function() {
    return function() {
      return this.name;
    };
  }
}

console.log(o.getName()());   //Jack
var name = 'Jack';

var o = {
  name : 'bingdian',

  getName : function() {
    var self = this;
    return function() {
      return self.name;
    };
  }
}

console.log(o.getName()());   //bingdian

內存泄露

function assignHandler() {
  var el = document.getElementById('demo');
  el.onclick = function() {
    console.log(el.id);
  }
}
assignHandler();

以上代碼創(chuàng)建了作為el元素事件處理程序的閉包,而這個閉包又創(chuàng)建了一個循環(huán)引用,只要匿名函數(shù)存在,el的引用數(shù)至少為1,因些它所占用的內存就永完不會被回收。

function assignHandler() {
  var el = document.getElementById('demo');
  var id = el.id;

  el.onclick = function() {
    console.log(id);
  }

  el = null;
}
assignHandler();

把變量el設置null能夠解除DOM對象的引用,確保正常回收其占用內存。

模仿塊級作用域

任何一對花括號({和})中的語句集都屬于一個塊,在這之中定義的所有變量在代碼塊外都是不可見的,我們稱之為塊級作用域。

(function(){
  //塊級作用域
})();

閉包的應用

保護函數(shù)內的變量安全。如前面的例子,函數(shù)outer中i只有函數(shù)inner才能訪問,而無法通過其他途徑訪問到,因此保護了i的安全性。
在內存中維持一個變量。如前面的例子,由于閉包,函數(shù)outer中i的一直存在于內存中,因此每次執(zhí)行rs(),都會給i加1。

相關文章

最新評論