淺談javascript中的的閉包

閉包可以說(shuō)是javascript中最令人迷惑的概念了。需要我們?cè)趯?shí)踐中去慢慢理解,在實(shí)際編碼中,由于閉包的效率和會(huì)產(chǎn)生大量無(wú)法銷毀的內(nèi)存,所以原則是盡量少使用閉包,但是作為javascript中的一個(gè)特別的概念,理解閉包是很重要的。閉包像是一種突破javascript中作用域限制的利劍。下面我們就從javascript中的作用域鏈談起,簡(jiǎn)單講講閉包的概念和理解。

作用域鏈

javascript中沒(méi)有大括號(hào)級(jí)的作用域,但是javascript中擁有函數(shù)作用域。在某函數(shù)內(nèi)部定義的變量,在函數(shù)外部是不可見(jiàn)的。
如下面這段代碼:

var a=1;
function f() {
  var b = 1;
  return a;
}
f();
b

這段代碼在函數(shù)外部訪問(wèn)了函數(shù)f中定義的變量的b,但是是不行的,會(huì)報(bào)如下錯(cuò)誤:

ReferenceError: b is not defined
data:,/* 通過(guò) Firebug 命令行執(zhí)行的表達(dá)式: */%0Avar%20a%3D1%3B%0Afunction%20f()%20%7B%0A%20%20var%20b%20%3D%201%3B%0A%20%20return%20a%3B%0A%7D%0Af()%3B%0Ab
Line 8

下面這個(gè)例子,我們?cè)趏uter中定義了另一個(gè)函數(shù)inner,那么在inner中可以訪問(wèn)的變量即來(lái)自他自己的作用域,也來(lái)自他的父親作用域,也就函數(shù)outer,所以這樣就形成了一條作用域鏈。

var global = 1;
function outer() {
  var outer_local = 2;
  function inner() {
    var inner_local = 3;
    return inner_local + outer_local + global;
  }
  return inner();
}
outer();

當(dāng)我們?cè)L問(wèn)執(zhí)行outer函數(shù)的時(shí)候,返回的結(jié)果是6.
所以在這個(gè)例子中,可以通過(guò)inner訪問(wèn)到outer和global的變量,這就是作用域鏈。

引出閉包

我們看下面這段代碼

var a = 'global variable';
var F = function () {
  var b = 'local variable';
  var N = function () {
    var c = 'inner local';
  };
};

我們定義了一個(gè)全局變量a,一個(gè)函數(shù)F,函數(shù)內(nèi)部定義了變量b,以及一個(gè)私有函數(shù)N,私有函數(shù)內(nèi)部定義了變量c。
我們通過(guò)圖展示這些變量作用域之間的關(guān)系。
全局變量:

closure1.png

全部的變量:

closure.png

這個(gè)圖不太標(biāo)準(zhǔn)。但我們可以理解一下:
如果我們是a,那么我們就在全局作用域中,而如果是b,我們就位于函數(shù)f的作用域內(nèi),在這個(gè)作用域里,我們可以訪問(wèn)函數(shù)f中的變量也可以訪問(wèn)函數(shù)f外的全局作用域的變量,這就形成了一個(gè)作用域鏈,同樣對(duì)于c點(diǎn),是位于函數(shù)n中的變量,在c點(diǎn)的作用域我們可以訪問(wèn)圖中所有的變量。根據(jù)圖中,我們大致可以把整個(gè)空間分為 全局空間 ,F(xiàn)空間,和N空間。顯然,a與b是不連通的,也就是說(shuō)我們?cè)赼點(diǎn)是無(wú)法訪問(wèn)到b的,同理,顯然a也是無(wú)法訪問(wèn)c點(diǎn)的。
這時(shí)候,通過(guò)閉包的話,我們可以把N與b連通起來(lái)。將N的空間擴(kuò)展到F以外,并止步于全局空間。這就是** 閉包 **!

closure2.png

使用閉包后的結(jié)果就跟上圖一樣。
如果變成上圖的這樣的話,這樣N就位于全局空間和a是在同一空間的,但是由于函數(shù)N還記得被定義時(shí),所處的環(huán)境,因此他依然可以訪問(wèn)F空間并使用b,這有很有趣,因?yàn)檫@個(gè)時(shí)候,N與a處于同一空間,N可以訪問(wèn)b,而a卻不能,這就是閉包的神奇作用。這就是讓N突破了作用域鏈,跳到了全局空間,那么我們想象這是如何做到的,其實(shí)很簡(jiǎn)單,只要通過(guò)F將N返回出來(lái),到全局空間就可以了。

利用閉包突破作用域鏈的三種方法

下面我們具體講解三種使用閉包突破作用域鏈的方法。

閉包1

首先,我們對(duì)上面那個(gè)函數(shù)做一些修改。

var a = 'global variable';
var F = function () {
  var b = 'local variable';
  var N = function () {
    var c = 'inner local';
    return b;
  };
  return N;
};

var inner = F();
inner();

我們?cè)俸瘮?shù)F中返回了函數(shù)N,并在函數(shù)N中返回b。
函數(shù)N有自己的私有空間,同時(shí)也可以訪問(wèn)f空間和全局空間,所以b對(duì)他來(lái)說(shuō)是可見(jiàn)的。因?yàn)镕是可以在全局空間中被調(diào)用的。所以我們可以將它的返回值富裕另外一個(gè)全局變量inner,這樣就生成了一個(gè)可以訪問(wèn)F私有空間的新的全局函數(shù)。

閉包2

第二種方法與第一種實(shí)現(xiàn)的方式不同,整體的思想還是一樣的。
我們?cè)谌致暶饕粋€(gè)變量inner,然后再在F中給他賦值,這樣,相當(dāng)于將N保存到全局作用域了。

var inner; // placeholder
var F = function () {
  var b = 'local variable';
  var N = function () {
    return b;
  };
  inner = N;
};
F();
inner();

閉包3

這次我們與前兩個(gè)不同,使用函數(shù)的參數(shù),該參數(shù)與函數(shù)的局部變量沒(méi)什么不同,但他們是隱式創(chuàng)建的,我們讓函數(shù)里的子函數(shù)返回函數(shù)的參數(shù)。這樣成為全局作用域里的子函數(shù),就可以訪問(wèn)到函數(shù)內(nèi)部的參數(shù)了。

function F(param) {
  var N = function () {
    return param;
  };
  param++;
  return N;
}

我們像如下這樣調(diào)用函數(shù)

> var inner = F(123);
> inner();
124

函數(shù)綁定的是作用域本身,而不是在函數(shù)定義時(shí)該作用域的變量或變量當(dāng)前的返回值。

小結(jié)

看完上面三種創(chuàng)建閉包的方式,我們是不是對(duì)閉包有了一定的模糊的認(rèn)識(shí)或者印象。

 事實(shí)上每個(gè)函數(shù)都可以認(rèn)為是閉包,因?yàn)槊總€(gè)函數(shù)都在其所在的作用域內(nèi)維護(hù)了某種私有關(guān)系的聯(lián)系。但大部分時(shí)候,該作用域在函數(shù)執(zhí)行完之后就自行銷毀了,除非像我們上面三種情況一樣使用了閉包,返回了一個(gè)內(nèi)部函數(shù),導(dǎo)致作用域被保持。
現(xiàn)在我們可以說(shuō),如果一個(gè)函數(shù)會(huì)在其父級(jí)作用域返回之后留住對(duì)父級(jí)作用域的連接的話,相關(guān)的閉包就會(huì)被創(chuàng)建起來(lái)。 

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容