摘要:條件閉包是允許函數(shù)訪問(wèn)局部作用域之外的數(shù)據(jù)。這就是需要理解閉包的核心內(nèi)容。我們可以通過(guò)創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期。而這個(gè)匿名函數(shù)內(nèi)部又創(chuàng)建并返回了一個(gè)訪問(wèn)的閉包。
前言
有很多人搞不清匿名函數(shù)和閉包這兩個(gè)概念,經(jīng)常混用。閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。匿名函數(shù)就是沒(méi)有實(shí)際名字的函數(shù)。
閉包 概念閉包,其實(shí)是一種語(yǔ)言特性,它是指的是程序設(shè)計(jì)語(yǔ)言中,允許將函數(shù)看作對(duì)象,然后能像在對(duì)象中的操作搬在函數(shù)中定義實(shí)例(局部)變量,而這些變量能在函數(shù)中保存到函數(shù)的實(shí)例對(duì)象銷(xiāo)毀為止,其它代碼塊能通過(guò)某種方式獲取這些實(shí)例(局部)變量的值并進(jìn)行應(yīng)用擴(kuò)展。
條件閉包是允許函數(shù)訪問(wèn)局部作用域之外的數(shù)據(jù)。即使外部函數(shù)已經(jīng)退出,外部函數(shù)的變量仍可以被內(nèi)部函數(shù)訪問(wèn)到。
因此閉包的實(shí)現(xiàn)需要三個(gè)條件:
內(nèi)部函數(shù)實(shí)用了外部函數(shù)的變量
外部函數(shù)已經(jīng)退出
內(nèi)部函數(shù)可以訪問(wèn)
function a() { var x = 0; return function(y) { x = x + y; // return x; console.log(x); } } var b = a(); b(1); //1 b(1); //2
上述代碼在執(zhí)行的時(shí)候,b得到的是閉包對(duì)象的引用,雖然a執(zhí)行完畢后,但是a的活動(dòng)對(duì)象由于閉包的存在并沒(méi)有被銷(xiāo)毀,在執(zhí)行b(1)的時(shí)候,仍然訪問(wèn)到了x變量,并將其加1,若再執(zhí)行b(1),則x是2,因?yàn)殚]包的引用b并沒(méi)有消除。(后面會(huì)解釋,閉包返回了函數(shù),函數(shù)可以創(chuàng)建獨(dú)立的作用域)
閉包,其實(shí)就是指程序語(yǔ)言中能讓代碼調(diào)用已運(yùn)行的函數(shù)中所定義的局部變量。
但是你只需要知道應(yīng)用的兩種情況即可——函數(shù)作為返回值,函數(shù)作為參數(shù)傳遞。
function fn() { var max = 10; return function bar(x) { if (x > max) { console.log(x); } }; } var f1 = fn(); f1(15);
如上代碼,bar函數(shù)作為返回值,賦值給f1變量。執(zhí)行f1(15)時(shí),用到了fn作用域下的max變量的值。至于如何跨作用域取值,可以參考上一篇文章。
var max = 10, fn = function(x) { if (x > max) { console.log(x); //15 } }; (function(f) { var max = 100; f(15); })(fn);
如上代碼中,fn函數(shù)作為一個(gè)參數(shù)被傳遞進(jìn)入另一個(gè)函數(shù),賦值給f參數(shù)。執(zhí)行f(15)時(shí),max變量的取值是10,而不是100。
上一篇講到自由變量跨作用域取值時(shí),曾經(jīng)強(qiáng)調(diào)過(guò):要去創(chuàng)建這個(gè)函數(shù)的作用域取值,而不是“父作用域”。理解了這一點(diǎn),以上兩端代碼中,自由變量如何取值應(yīng)該比較簡(jiǎn)單.
另外,講到閉包,除了結(jié)合著作用域之外,還需要結(jié)合著執(zhí)行上下文棧來(lái)說(shuō)一下。
在前面講執(zhí)行上下文棧時(shí),我們提到當(dāng)一個(gè)函數(shù)被調(diào)用完成之后,其執(zhí)行上下文環(huán)境將被銷(xiāo)毀,其中的變量也會(huì)被同時(shí)銷(xiāo)毀。
有些情況下,函數(shù)調(diào)用完成之后,其執(zhí)行上下文環(huán)境不會(huì)接著被銷(xiāo)毀。這就是需要理解閉包的核心內(nèi)容。
可以拿本文的之前代碼(只做注釋修改)來(lái)分析一下。
1//全局作用域 2 function fn() { 3 var max = 10; 4 // fn作用域 5 return function bar(x) { 6 if (x > max) { 7 console.log(x); 8 } 9 }; //bar作用域 10 } 11 var f1 = fn(); 12 f1(15);
全局作用域?yàn)椋捍a1-12行;fn作用域?yàn)椋捍a2-10行;bar作用域?yàn)椋捍a5-8行。
舉例第一步,代碼執(zhí)行前生成全局上下文環(huán)境,并在執(zhí)行時(shí)對(duì)其中的變量進(jìn)行賦值。此時(shí)全局上下文環(huán)境是活動(dòng)狀態(tài)。
第二步,執(zhí)行第17行代碼時(shí),調(diào)用fn(),產(chǎn)生fn()執(zhí)行上下文環(huán)境,壓棧,并設(shè)置為活動(dòng)狀態(tài)。
第三步,執(zhí)行完第17行,fn()調(diào)用完成。按理說(shuō)應(yīng)該銷(xiāo)毀掉fn()的執(zhí)行上下文環(huán)境,但是這里不能這么做。注意,重點(diǎn)來(lái)了:
因?yàn)閳?zhí)行fn()時(shí),返回的是一個(gè)函數(shù)。函數(shù)的特別之處在于可以創(chuàng)建一個(gè)獨(dú)立的作用域。而正巧合的是,返回的這個(gè)函數(shù)體中,還有一個(gè)自由變量max要引用fn作用域下的fn()上下文環(huán)境中的max。因此,這個(gè)max不能被銷(xiāo)毀,銷(xiāo)毀了之后bar函數(shù)中的max就找不到值了。
因此,這里的fn()上下文環(huán)境不能被銷(xiāo)毀,還依然存在與執(zhí)行上下文棧中。
——即,執(zhí)行到第18行時(shí),全局上下文環(huán)境將變?yōu)榛顒?dòng)狀態(tài),但是fn()上下文環(huán)境依然會(huì)在執(zhí)行上下文棧中。另外,執(zhí)行完第18行,全局上下文環(huán)境中的max被賦值為100。如下圖:
第四步,執(zhí)行到第20行,執(zhí)行f1(15),即執(zhí)行bar(15),創(chuàng)建bar(15)上下文環(huán)境,并將其設(shè)置為活動(dòng)狀態(tài)。
執(zhí)行bar(15)時(shí),max是自由變量,需要向創(chuàng)建bar函數(shù)的作用域中查找,找到了max的值為10。這個(gè)過(guò)程在作用域鏈一節(jié)已經(jīng)講過(guò)。
這里的重點(diǎn)就在于,創(chuàng)建bar函數(shù)是在執(zhí)行fn()時(shí)創(chuàng)建的。fn()早就執(zhí)行結(jié)束了,但是fn()執(zhí)行上下文環(huán)境還存在與棧中,因此bar(15)時(shí),max可以查找到。如果fn()上下文環(huán)境銷(xiāo)毀了,那么max就找不到了。
總結(jié):使用閉包會(huì)增加內(nèi)容開(kāi)銷(xiāo)
第五步,執(zhí)行完20行就是上下文環(huán)境的銷(xiāo)毀過(guò)程,這里就不再贅述了。
閉包與變量 概念例子閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值,閉包所保存的是整個(gè)變量對(duì)象,而不是某個(gè)特殊變量。
function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function() { return i; }; } return result; } var funcs = createFunctions(); //每個(gè)函數(shù)都輸出10 for (var i = 0; i < funcs.length; i++) { document.write(funcs[i]() + "
"); }
總結(jié):每個(gè)函數(shù)的作用域鏈中都保存著createFunctions()函數(shù)的活動(dòng)對(duì)象,所以它們引用的都是同一個(gè)變量i。當(dāng)createFunctions()函數(shù)返回后,變量i的值為10。
我們可以通過(guò)創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期。
function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function(x) { return function() { return x; }; }(i); } return result; } var funcs = createFunctions(); //循環(huán)輸出0-10 for (var i = 0; i < funcs.length; i++) { document.write(funcs[i]() + "
"); }
函數(shù)按值傳遞總結(jié):沒(méi)有直接把閉包賦值給數(shù)組,而是定義了一個(gè)匿名函數(shù),并通過(guò)立即執(zhí)行該匿名函數(shù)的結(jié)果賦值給數(shù)組,并帶了for循環(huán)的參數(shù)i進(jìn)去,讓x能找到傳入的參數(shù)值為0-10,這就解釋了函數(shù)參數(shù)是按值傳遞的,所以會(huì)將變量i的當(dāng)前值復(fù)制給參數(shù)x。而這個(gè)匿名函數(shù)內(nèi)部又創(chuàng)建并返回了一個(gè)訪問(wèn)x的閉包。這樣以來(lái)result數(shù)組中的每個(gè)函數(shù)都有自己x變量的一個(gè)副本,所以會(huì)符合我們的預(yù)期輸出不同的值。
函數(shù)傳參就兩個(gè)類型,基本類型和引用類型,大家糾結(jié)的都是引用類型的傳遞。
引用類型作為參數(shù)傳入函數(shù),傳的是個(gè)地址值,或者指針值,不是那個(gè)引用類型本身,它還好好的呆在堆內(nèi)存呢。賦值給argument的同樣是地址值或者指針。所以說(shuō)是value值傳遞一點(diǎn)沒(méi)錯(cuò),傳的是個(gè)地址值。通過(guò)兩個(gè)例子看懂就行了。
例子1:
function setName(obj) { obj.name = "aaa"; var obj = new Object(); // 如果是按引用傳遞的,此處傳參進(jìn)來(lái)obj應(yīng)該被重新引用新的內(nèi)存單元 obj.name = "ccc"; return obj; } var person = new Object(); person.name = "bbb"; var newPerson = setName(person); console.log(person.name + " | " + newPerson.name); // aaa | ccc
從結(jié)果看,并沒(méi)有顯示兩個(gè)"ccc"。這里是函數(shù)內(nèi)部重寫(xiě)了obj,重寫(xiě)的obj是一個(gè)局部對(duì)象。當(dāng)函數(shù)執(zhí)行完后,立即被銷(xiāo)毀。
引用值:對(duì)象變量它里面的值是這個(gè)對(duì)象在堆內(nèi)存中的內(nèi)存地址。因此如果按引用傳遞,它傳遞的值也就是這個(gè)內(nèi)存地址。那么var obj = new Object();會(huì)重新給obj分配一個(gè)地址,比如是0x321了,那么它就不在指向有name = "aaa";屬性的內(nèi)存單元了。相當(dāng)于把實(shí)參obj和形參obj的地址都改了,那么最終就是輸出兩個(gè)ccc了。
例子2
var a = { num:"1" }; var b = { num:"2" }; function change(obj){ obj.num = "3"; obj = b; return obj.num; } var result = change(a); console.log(result + " | " + a.num); // 2 | 3
閉包中使用this對(duì)象 概念首先把a(bǔ)的值傳到change函數(shù)內(nèi),obj.num = "3";后a.name被修改為3;
a的地址被換成b的地址;
返回此時(shí)的a中a.num。
this對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的:全局函數(shù)中,this等于window;當(dāng)函數(shù)被作用某個(gè)對(duì)象的方法調(diào)用時(shí),this等于那個(gè)對(duì)象。
但在匿名函數(shù)中,由于匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此this對(duì)象通常指向window(在通過(guò)call或apply函數(shù)改變函數(shù)執(zhí)行環(huán)境的情況下,會(huì)指向其他對(duì)象)。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window"
通過(guò)修改把作用域中的this對(duì)象保存在一個(gè)閉包能夠訪問(wèn)到的變量里,就可以讓閉包訪問(wèn)該對(duì)象了。如下代碼:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()()); //"MyObject"變量聲明提前
var scope="global"; function scopeTest() { console.log(scope); var scope="local"; } scopeTest(); //undefined
此處的輸出是undefined,并沒(méi)有報(bào)錯(cuò),這是因?yàn)樵谇懊嫖覀兲岬降暮瘮?shù)內(nèi)的聲明在函數(shù)體內(nèi)始終可見(jiàn),上面的函數(shù)等效于:
var scope="global"; function scopeTest() { var scope; console.log(scope); scope="local"; } scopeTest(); //undefined
注意,如果忘記var,那么變量就被聲明為全局變量了。結(jié)果就是global
沒(méi)有塊級(jí)作用域和其他我們常用的語(yǔ)言不同,在Javascript中沒(méi)有塊級(jí)作用域:
function scopeTest() { var scope = {}; if (scope instanceof Object) { var j = 1; for (var i = 0; i < 10; i++) { console.log(i); //輸出0-9 } console.log(i); //輸出10 } console.log(j); //輸出1 } scopeTest();
在javascript中變量的作用范圍是函數(shù)級(jí)的,即在函數(shù)中所有的變量在整個(gè)函數(shù)中都有定義,這也帶來(lái)了一些我們稍不注意就會(huì)碰到的“潛規(guī)則”:
var scope = "hello"; function scopeTest() { console.log(scope);//① var scope = "no"; console.log(scope);//② }
在①處輸出的值竟然是undefined,簡(jiǎn)直喪心病狂啊,我們已經(jīng)定義了全局變量的值啊,這地方不應(yīng)該為hello嗎?其實(shí),上面的代碼等效于:
var scope = "hello"; function scopeTest() { var scope; console.log(scope);//① scope = "no"; console.log(scope);//② }
聲明提前、全局變量?jī)?yōu)先級(jí)低于局部變量,根據(jù)這兩條規(guī)則就不難理解為什么輸出undefined了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/83141.html
摘要:如何使用閉包所以這時(shí)候我們就要用閉包去解決這個(gè)問(wèn)題了,先看代碼。計(jì)數(shù)器為這時(shí)候的就形成了一個(gè)閉包。一個(gè)閉包由兩部分組成,函數(shù)和創(chuàng)建該函數(shù)的環(huán)境。就是創(chuàng)建了一個(gè)匿名函數(shù)調(diào)用函數(shù)解除對(duì)匿名函數(shù)的引用,以便釋放內(nèi)存 古老定義 閉包(closure),是指函數(shù)變量可以保存在函數(shù)作用域內(nèi),因此看起來(lái)是函數(shù)將變量包裹了起來(lái)。 //根據(jù)定義,包含變量的函數(shù)就是閉包 function foo() { ...
摘要:再看一段代碼這樣就清晰地展示了閉包的詞法作用域能訪問(wèn)的作用域?qū)?dāng)做一個(gè)值返回執(zhí)行后,將的引用賦值給執(zhí)行,輸出了變量我們知道通過(guò)引用的關(guān)系,就是函數(shù)本身。 在正式學(xué)習(xí)閉包之前,請(qǐng)各位同學(xué)一定要確保自己對(duì)詞法作用域已經(jīng)非常的熟悉了,如果對(duì)詞法作用域還不夠熟悉的話,可以先看: 深入理解閉包之前置知識(shí)---作用域與詞法作用域 前言 現(xiàn)在去面試前端開(kāi)發(fā)的崗位,如果你的面試官也是個(gè)前端,并且不是太...
摘要:建筑的頂層代表全局作用域。實(shí)際的塊級(jí)作用域遠(yuǎn)不止如此塊級(jí)作用域函數(shù)作用域早期盛行的立即執(zhí)行函數(shù)就是為了形成塊級(jí)作用域,不污染全局。這便是閉包的特點(diǎn)吧經(jīng)典面試題下面的代碼輸出內(nèi)容答案?jìng)€(gè)如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫(xiě)在前面 這一系列的筆記是在《javascript高級(jí)程序設(shè)計(jì)》讀書(shū)筆記系列的升華版本,旨在將零碎...
摘要:閉包與立即執(zhí)行函數(shù)前言最近在細(xì)讀高級(jí)程序設(shè)計(jì),對(duì)于我而言,中文版,書(shū)中很多地方翻譯的差強(qiáng)人意,所以用自己所理解的,嘗試解讀下。作用域鏈的這種配置機(jī)制引出了一個(gè)值得注意的副作用,即閉包只能取得包含任何變量的最后一個(gè)值。 閉包與立即執(zhí)行函數(shù) 前言:最近在細(xì)讀Javascript高級(jí)程序設(shè)計(jì),對(duì)于我而言,中文版,書(shū)中很多地方翻譯的差強(qiáng)人意,所以用自己所理解的,嘗試解讀下。如有紕漏或錯(cuò)誤,會(huì)...
摘要:定義函數(shù)表達(dá)式的方式有兩種函數(shù)聲明。不過(guò),這并不是匿名函數(shù)唯一的用途??梢允褂妹瘮?shù)表達(dá)式來(lái)達(dá)成相同的結(jié)果閉包匿名函數(shù)和閉包是兩個(gè)概念,容易混淆。匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其對(duì)象通常指向通過(guò)改變函數(shù)的執(zhí)行環(huán)境的情況除外。 定義函數(shù)表達(dá)式的方式有兩種: 函數(shù)聲明。它的重要特征就是 函數(shù)聲明提升(function declaration hoisting) 即在執(zhí)行代碼之前會(huì)...
閱讀 1364·2021-11-23 09:51
閱讀 3530·2021-09-06 15:00
閱讀 1048·2021-08-16 10:57
閱讀 1432·2019-08-30 12:46
閱讀 986·2019-08-29 12:22
閱讀 1671·2019-08-29 11:07
閱讀 3203·2019-08-26 11:23
閱讀 3044·2019-08-23 15:14