国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript閉包,只學這篇就夠了

CoderBear / 1336人閱讀

摘要:當在中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了和的當前值的值為因為循環已經結束,的值為。最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。

閉包不是魔法

這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。

其實只要理解了核心概念,閉包并不是那么的難于理解。但是,網上充斥了太多學術性的文章,對于新手來說,看完這些文章可能會更加一頭霧水。

這篇文章面向的是使用主流開發語言的程序員,如果你能讀懂下面這段代碼,恭喜你,你可以開始JavaScript閉包的學習之旅了。

function sayHello(name) {
    var text = "Hello" + name;
    var say = function() {
        console.log(text);
    }
    say();
}
sayHello("Joe");

我相信你一定看懂了,那我們就開始吧!

閉包的一個例子

舉例之前,我們先用兩句話概括一下:

閉包是支持一類函數特性的一種方式(如果你還不知道什么是一類函數,請自行百度);它是一個表達式,這個表達式可以在其作用域(當它被初次定義時)內引用變量,或者被賦值給一個變量,或者被當做一個變量傳遞給某個函數,甚至被當作一個函數的執行結果被返回出去。

閉包也可以看作是某個函數被調用時分配的棧幀,而且當這個函數返回結果之后它也不會被回收(就好像它被分配給了堆,而不是棧)

下面的例子返回了對一個方法的引用:

function sayHello2(name){
    var text= "Hello" + name; //局部變量
    var say=function(){
        console.log(text);
    }
    return say;
}
var say2=sayHello2("Bob");
say2();//logs="Hello Bob"

我想大多數JavaScript程序員都能理解上面代碼中一個函數的引用是如何被賦值給一個變量(say2)的。如果你不清楚的話,最好在繼續了解閉包之前弄清楚。使用C語言的程序員或許會認為這個函數是指向另一個函數的指針,并且變量saysay2也同樣是指向函數的指針。

然而C語言中指向函數的指針和JavaScript中對一個函數的引用有很大的不同。在JavaScript中,你可以把引用函數的變量當作同時擁有兩個指針:一個指向函數,另一個隱形地指向閉包。

上面的代碼中生成了一個閉包是因為匿名函數function(){console.log(text);}被定義在了另外一個函數sayHello2()中。在JavaScript中,如果你在一個函數中定義了另外一個函數,那么你就創建了一個閉包。

在C語言或者其他流行的開發語言當中,函數返回之后,所有局部變量都不能再被訪問,因為棧幀已經被銷毀了。

在JavaScript中,如果在一個函數中定義了另外一個函數,即使從被調用的函數中返回,局部變量依然能夠被訪問到。正如上面例子中我們在得到sayHello2()的返回值之后又調用了say2()一樣。需要注意到,我們調用的代碼中引用了函數sayHello2()中的局部變量text

function(){console.log(text);} //say2.toString()的輸出結果;

觀察say2.toString()的輸出結果,我們會發現代碼指向變量text。這個匿名函數能夠引用值為Hello Bob的變量text是因為sayHello2()的局部變量被保留在了閉包中。

在JavaScript中神奇的地方在于引用一個函數的同時會有一個秘密的引用指向在這個函數內部創建的閉包,類似于委托一個方法指針加一個隱藏的對象引用。

更多例子

當你讀到很多關于閉包的文章時,總會感覺一頭霧水,但是當你看到一些應用的例子時,你就能清晰的理解閉包是如何工作的了。下面是我推薦的一些例子,希望大家能夠認真研究直到真正清楚閉包是如何工作的。如果在你沒有完全理解的情況下就開始使用閉包,你很快就會成為很多奇怪bug的創造者。

下面這個例子展示了局部變量不是被復制,而是被保留在了引用當中。這是當外部函數存在的情況下將棧幀保存在內存中的方法之一。

function say667(){
//處于閉包中的局部變量
var num=42;
var say=function(){console.log(num);}
num++;
return say;
}
var sayNumber=say667();
sayNumber();//logs 43

下面例子中的三個全局函數有對同一個閉包的共同引用,因為他們都在setupSomeGlobals()中被定義。

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  //處于閉包中的局部變量
  var num = 42;
  // 用全局變量存儲對函數的引用
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5    

當這三個函數被創建時,它們能夠共享對同一個閉包的訪問-即對setupSomeGlobals()中的局部變量的訪問。

需要注意到在上述例子中,如果你再次調用setupSomeGlobals(),會創建一個新的閉包。gLogNumber()gSetNumber()gLogNumber()會被帶有新閉包的函數重寫(在JavaScript中,當在一個函數中定義另外一個函數時,重新調用外部函數會導致內部函數被重新創建)。

下面這個例子對很多人來說都難以理解,所以你更需要真正理解它。在循環中定義函數時要格外小心:閉包中的局部變量或許不會和你的預想的一樣。

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = "item" + i;
        result.push( function() {console.log(item + " " + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3次

注意到result.push( function() {console.log(item + " " + list[i])}result數組中插入了三次對匿名函數的引用。如果你對匿名函數不太熟悉,可以想象成下面的代碼:

pointer=function(){console.log(item+""+list[i])};
result.push(pointer);

需要注意到,當你運行上面的例子時,item2 undefined被打印了三次!這是因為像前一個例子中提到的,buildList的局部變量只有一個閉包。當在fnlist[j]()中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了iitem的當前值(i的值為3因為循環已經結束,item的值為item2)。因為我們從0開始計數所以item的值為item2,而i++會使i的值變為3

下面這個例子展示了閉包在退出之前包含了外部函數中定義的任何局部變量。注意到變量alice其實是在匿名函數之后定義的。匿名函數先定義,但是當它被調用時它能夠訪問alice,因為alice和匿名函數處于同一作用域(JavaScript會進行變量提升)。sayAlice()()只是直接調用了sayAlice()返回的函數引用-但結果卻和之前一樣,只不過沒有臨時變量而已。

function sayAlice() {
    var say = function() { console.log(alice); }
    var alice = "Hello Alice";
    return say;
}
sayAlice()();// logs "Hello Alice"

注意到變量say也在閉包中,能夠被任何在sayAlice()中定義的函數訪問,或者在內部函數中被遞歸調用。

最后一個例子展現了每次調用都為局部變量創建一個獨立閉包。不是每個函數定義都會有一個閉包,而是每次函數調用產生一個閉包。

function newClosure(someNum, someRef) {
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log("num: " + num +
            "; anArray: " + anArray.toString() +
            "; ref.someVar: " + ref.someVar + ";");
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
總結

如果你對于閉包的概念依然不清晰,那么最好的方式就是運行一下上面的例子,看看會發生什么。讀懂一篇長篇大論要比理解一個例子難的多。我對與閉包和棧幀的解釋在技術上并不完全正確-而是為了幫助理解而簡化了。如果這些基本點都掌握之后,你就可以朝著更細微之處進發了。

最后總結幾點:

當你在一個函數中定義另外一個函數時,你就使用了閉包。

當你在函數中使用eval()時,你就使用了閉包。你在eval中用到的文字可以指向外部函數的局部變量,而且在eval中你也可以使用eval("val foo=...")來創建局部變量。

當你在函數中使用new Function(...)時,不會創建一個閉包(這個新的函數不能引用外部函數的局部變量)。

JavaScript中的閉包就好像保存了一份局部變量的備份,他們保持在函數退出時的狀態。

最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。

當一個帶有閉包的函數被調用時,總會保存一組新的局部變量。

兩個看似代碼相同的函數卻有不同的行為,是因為隱藏的閉包在作怪。我不認為JavaScript代碼能夠判斷出一個函數引用是否有閉包。

如果你嘗試做任何動態代碼的改動(例如:myFunction = Function(myFunction.toString().replace(/Hello/,"Hola"));),如果myFunction是個閉包,那就不會起作用(當然,你不會想在運行時里進行源代碼的字符串替換,除非...)。

在函數中定義多層函數是有可能的,這樣你就可以得到多個級別的閉包。

我認為在通常情況下,閉包是函數及被捕獲的變量的術語,請注意在這篇文章里我沒有用到閉包的定義。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87301.html

相關文章

  • JS正則表達式入門,看這篇夠了

    摘要:如果遇到非常的復雜的匹配,正則表達式的優勢就更加明顯了。關于正則表達式書寫規則,可查看,上面說的很清楚了,我就不貼出來了。替換與正則表達式匹配的子串,并返回替換后的字符串。結語正則表達式并不難,懂了其中的套路之后,一切都變得簡單了。 前言 在正文開始前,先說說正則表達式是什么,為什么要用正則表達式?正則表達式在我個人看來就是一個瀏覽器可以識別的規則,有了這個規則,瀏覽器就可以幫我們判斷...

    wenzi 評論0 收藏0
  • 前端異常監控-看這篇夠了

    摘要:前端異常監控如果是移除的流程,那么編程就一定是將放進去的流程。過濾掉運行時錯誤上報加載錯誤事件捕獲異常最新的規范中定義了事件用于全局捕獲對象沒有處理器時異常情況。 前端異常監控 如果debug是移除bug的流程,那么編程就一定是將bug放進去的流程。如果沒有用戶反饋問題,那就代表我們的產品棒棒噠,對不對? 主要內容 Web規范中相關前端異常 異常按照捕獲方式分類 異常的捕獲方式 日志...

    Aklman 評論0 收藏0
  • Lombok 看這篇夠了

    摘要:注解在類上為類提供一個全參的構造方法,加了這個注解后,類中不提供默認構造方法了。這個注解用在類上,使用類中所有帶有注解的或者帶有修飾的成員變量生成對應的構造方法。 轉載請注明原創地址:http://www.54tianzhisheng.cn/2018/01/07/lombok/ showImg(http://ohfk1r827.bkt.clouddn.com/blog/180107/7...

    LeanCloud 評論0 收藏0
  • 從0到1學AJAX,這篇夠了!(進階三)

    摘要:一定在發送請求之前注冊不管同步或者異步為了讓這個事件可以更加可靠一定觸發,一定是先注冊了解同步模式即可,切記不要使用同步模式。至此,我們已經大致了解了的基本。一種數據描述手段,基本現在的項目不用了,淘汰的原因數據冗余太多。 什么是ajax? AJAX 就是瀏覽器提供的一套 API,可以通過 JavaScript 調用,從而實現通過代碼控制請求與響應。實現網絡編程 1、使用 AJAX 的...

    CoderDock 評論0 收藏0

發表評論

0條評論

CoderBear

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<