用最通俗易懂的代碼協助新手理解javascript閉包

來源:互聯網
上載者:User

我同樣也是個javascript新手,怎麼說呢,先學的jquery,精通之後發現了javascript的重要性,再回過頭來學javascript物件導向編程。

最近看了幾篇有關javascript閉包的文章,包括最近正火的湯姆大叔系列,還有《javascript進階程式設計》中的文章,……我看不懂,裡面有些代碼是在大學教科書中看都沒看過的,天書一般。幸好最近遇到兩本好書《ppk on javascript》和《object-oriented JavaScript》,正字閱讀中,後者還沒有中文版,但前者還是建議看原版,寫的不複雜,有興趣的朋友可以看看,適合想進階的朋友。     

今天就結合這兩本書,用最淺顯的語言和最通俗的方式談談javascript中的閉包,因為也是新手,所以有有誤的地方請各位指出,謝謝

一.   準備知識

1.函數作為函數的參數

在學習javascript中,你始終要有一個有學習與其他語言不同的概念:函數(function)不麼特殊的東西,它也是一種資料,與bool ,string,number沒有什麼兩樣。

函數的參數可以string,number,bool如:

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

但同樣也可以傳入函數。對你沒有聽錯,函數的參數是函數!加入你有以下兩個函數:

//把三個數翻一倍
function multiplyByTwo(a, b, c) {
var i, ar = [];

for(i = 0; i < 3; i++) {
ar[i] = arguments[i] * 2;
}
return ar;
}

 

//把數加一
function addOne(a) {

return a + 1;

}

然後這麼使用

var myarr = [];

//先把每個數乘以二,用了一個迴圈

myarr = multiplyByTwo(10, 20, 30);

//再把每個數加一,又用了一個迴圈

for (var i = 0; i < 3; i++) {myarr[i] = addOne(myarr[i]);}

 要注意到其實這個過程用了兩個迴圈,還是有提升的空間的,不如這麼做:

function multiplyByTwo(a, b, c, addOne) {

var i, ar = [];

for(i = 0; i < 3; i++) {

ar[i] = addOne (arguments[i] * 2);

}
return ar;
}

這樣就把函數當做參數傳遞進去了,並且在第一個迴圈中直接調用。這樣的函數就是著名的回呼函數(Callback function)

2.函數作為傳回值

在函數中可以有傳回值,但是我們一般都熟悉數值的返回,如

function ex(){

return 12

}

但你一旦意識到函數只是一種資料的話,你就可以想到同樣可以返回函數。注意看下面這個函數:

function a() {

alert('A!');

return function(){

alert('B!');

};

}

它返回了一個彈出”B!”的函數。接下來使用它:

var newFunc = a();
newFunc();

結果是什麼呢?首先執行a()的時候,彈出”A!”,此時newFunc接受了a的傳回值,一個函數——此時newFunc就變成了那個被a返回的函數,再執行newFunc時,彈出”B!”

3.javascript的範圍

javascript的範圍很特別,它是以函數為單位的,而不是像其他語言以塊為單位(如一個迴圈中),看下面這個例子:

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

 

如果你此時試圖想得到b的值:在firebug中試圖輸入alert(b)的話,你會得到錯誤提示:

b is not defined

 

為什麼你可以這麼理解:你所在的編程環境或者視窗是最頂級的一個函數,好像一個宇宙,但是b只是在你內建函式的一個變數,宇宙中的小星球上的一個點,你很難找到它,所以在這個環境中你不能調用它的;反之這個內建函式可以調用變數a,因為它暴露在整個宇宙中,無處藏身,同時也可以調用b,因為它就在自己的星球上,函數內部。

就上面這個例子說:

  1. 在f()外,a可見,b不可見
  2. 在f()內,a可見,b也可見

再複雜點:

var a = 1; //b,c在這一層都不可見

function f(){

var b = 1;

function n() { //a,b,c對這個n函數都可以調用,因為a,b暴露在外,c又是自己內部的

var c = 3;

}

}

問你,函數b可以調用變數c嗎?不行,記住javascript的範圍是以函數為單位的,c在n的內部,所以對f來說是不可見的。

開始正式談閉包:

首先看這個圖:

 

假設G,F,N 分別代表三個層次的函數,層次,a,b,c分別是其中的變數。根據上面談到的範圍,我們有如下結論:

  1. 如果你在a點,你是不可以引用b的,因為b對你是不可見的
  2. 只有c可以引用b

閉包的弔詭之處的就在於發生了如下情況:

 

N突破了F的限制!跑到於a同一層了!因為函數只認它們在定義時所處的環境而不是執行時,這點很重要),N中的c仍然可以訪問b!此時的a還是不可以訪問b!

但是這是怎麼實現的呢?如下:

閉包1

function f(){

var b = "b";

return function(){ //沒有名字的函數,所以是匿名函數

return b;

}

}

注意返回的函數可以訪問它父親函數中的變數b

此時如果你想取b的值,當然是undefined

但是如果你這麼做:

var n = f();

n();

你可以取到b的值了!雖然此時n函數在f的外面,b又屬於f內部的變數,但是f內部出了一個內鬼,返回了b的值……

現在大家有點感覺了吧

閉包2

var n;

function f(){

var b = "b";

n = function(){

return b;

}

}

如果此時調用f會怎麼樣?那就產生了一個n的全域範圍函數,但是它卻能訪問f的內部,照樣返回b的值,與上面有異曲同工之妙!

閉包3:

你還可以用閉包訪問函數的參數

function f(arg) {

var n = function(){

return arg;

};

arg++;

return n;

}

此時如果使用:

var m = f(123);

m();

 

結果是124

因為此時f中返回的匿名函數經過了兩道轉手,先給n,再賦給外面的m,但本質沒有變,把定義時父函數的參數返回了

閉包4

var getValue, setValue;

function() {

var secret = 0;

getValue = function(){

return secret;

};

setValue = function(v){

secret = v;

};

})

運行:

getValue()

0

setValue(123)

getValue()

123

這個就不用解釋了吧,如果你有物件導向語言基礎的話(如C#),這裡的getValue和setValue就類似於一個對象的屬性訪問器,你可以通過這兩個訪問器來賦值和取值,而不是能訪問其中內容

 

其實書中還有幾個閉包的例子,但是原理用上面四個就足夠了,希望能起拋磚引玉的作用,給javascript進階者對閉包有一個更深刻的理解

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.