摘要:局部變量只在當(dāng)前函數(shù)體內(nèi)有效,出了函數(shù)體,就上一級(jí)的范圍,局部變量無效。但是在中,函數(shù)內(nèi)部有一個(gè)函數(shù),它的函數(shù)體內(nèi)的是指中聲明的局部變量,而非全局變量。這就是一個(gè)非常典型的閉包了。
函數(shù)內(nèi)部的函數(shù):私有函數(shù)嚴(yán)格的講,閉包常常表現(xiàn)為一個(gè)函數(shù)內(nèi)部的函數(shù),它使用了非自己定義的、自己所在作用域內(nèi)的變量,并且使這些變量突破了作用域的限制。
首先,我們從這個(gè)內(nèi)部函數(shù)去說開,因?yàn)檫@個(gè)是形式上的,如果一開始講作用域,有點(diǎn)故意。閉包在形式上就是函數(shù)內(nèi)部的函數(shù),比如:
jQuery(function($){ function message(msg) { alert(msg); } if($(window).width() > 1000) message("window寬度大于1000"); });
如果你用jquery,這段代碼應(yīng)該經(jīng)常使用吧。如果你仔細(xì)去觀察,就會(huì)發(fā)現(xiàn),第一個(gè)function被作為參數(shù),傳給了jQuery()這個(gè)函數(shù),而在function內(nèi),又有一個(gè)message()函數(shù)。所有的jQuery代碼被放在第一個(gè)function中去處理。第二個(gè)函數(shù)就是函數(shù)體內(nèi)部的函數(shù),這個(gè)函數(shù)在函數(shù)體內(nèi)聲明,一旦外層函數(shù)執(zhí)行完畢,那么這個(gè)函數(shù)就失去了作用,在jQuery()外無法使用message(),因此,message()是第一個(gè)函數(shù)內(nèi)部的私有函數(shù)。
變量的作用域函數(shù)內(nèi)部的變量有兩種,一種是局部變量,一種是全局變量。局部變量只在當(dāng)前函數(shù)體內(nèi)有效,出了函數(shù)體,就上一級(jí)的范圍,局部變量無效。
var age = 10; function a(age) { return age + 1; } function b(_age) { return age + _age; } function c(_age) { var age = 11; function add() { return age + _age; } return add(); } alert(a(9)); // 10 : 9 + 1 alert(b(2)); // 12 : 10 + 2 alert(c(5)); // 16 : 11 + 5
在上面的代碼中,我們看b和c函數(shù)。b函數(shù)中的age直接引用了全局變量age(10),而c函數(shù)中重新聲明了局部變量age,因此,全局變量age在c函數(shù)中無效。
但是在c中,函數(shù)內(nèi)部有一個(gè)函數(shù)add(),它的函數(shù)體內(nèi)的age是指c中聲明的局部變量,而非全局變量age。從這個(gè)例子里,反映出了變量的作用域,函數(shù)內(nèi)的函數(shù)體里,如果沒有聲明局部變量,就會(huì)承認(rèn)其父級(jí)甚至祖先級(jí)函數(shù)的變量,以及全局變量。
閉包怎么樣才算是一個(gè)閉包呢?我們來看下面的代碼:
function a() { var age = 10; return function(){ return age; } } var age = a(); alert(age()); // 10
按照我們前面說的作用域,在上面這段代碼中age是a()的局部變量,按道理,出了函數(shù),就不能被訪問了。但是,a()返回了一個(gè)私有函數(shù),個(gè)這個(gè)函數(shù)返回了age,這導(dǎo)致我們可以在a()外部,仍然可以訪問到本來是局部變量的age,這個(gè)時(shí)候,我們就把這個(gè)內(nèi)部函數(shù)稱為閉包。它的原理就是,函數(shù)內(nèi)部的函數(shù),可以訪問父函數(shù)的局部變量。
綜合上面的闡述,我們要這樣去理解閉包:
閉包是一個(gè)函數(shù),它使用了自己之外的變量。
閉包是一個(gè)作用域。
閉包是“由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體”。
嚴(yán)格的講,閉包常常表現(xiàn)為一個(gè)函數(shù)內(nèi)部的函數(shù),它使用了非自己定義的、自己所在作用域內(nèi)的變量,并且使這些變量突破了作用域的限制。
一個(gè)典型的閉包:
函數(shù)內(nèi)的函數(shù)
這個(gè)內(nèi)部函數(shù)引用了父函數(shù)的局部變量
這個(gè)內(nèi)部函數(shù)使引用的變量突破了作用域限制
var a = 1;
function fun() {return a + 1;}
alert(fun());
這也可以算作一個(gè)閉包,a()引用了它之外定義的變量。但是這不算嚴(yán)格的閉包,因?yàn)樗鼪]有在突破作用域的這個(gè)點(diǎn)上表現(xiàn)出來。
var a = 1; function fun() { var b = 2; return function(){ return a + ++b; }; } var c = fun(); alert(c()); // 4 alert(c()); // 5
這就是一個(gè)非常典型的閉包了。而且為什么alert(c())兩次的值不同,我們還會(huì)在下面解釋到。
為了讓你更加明晰的和你曾經(jīng)開發(fā)過的案例聯(lián)系在一起,我們來看我們?cè)?jīng)做過的這樣的事:
define(function(){ var age = 10; function getAge() { return age; } function grow() { age ++; } return { age : getAge, grow : grow }; });
這是我們?cè)趓equire.js中的一種寫法,把它還原為我們熟悉的閉包模式:
function Cat(){ var age = 10; function getAge() { return age; } function grow() { age ++; } return { ageAge : getAge, grow : grow }; }; var cat = Cat(); var age = cat.getAge(); alert(age); // 10 cat.grow(); age = cat.getAge(); alert(age); // 11從內(nèi)存看閉包
現(xiàn)在,我們就要來解釋為什么上面的alert()兩次的結(jié)果不同的原因了。
首先,我們來看下普通的函數(shù)聲明和使用過程中內(nèi)存的變化:
function fun(a,b) { return a+b; } alert(fun(1,2)); alert(fun(3,4));
上面是我們沒有遇到閉包的情況,內(nèi)存我們這樣來畫(注意,我這里只是抽象的畫出內(nèi)存變化,而不是真實(shí)的javascript內(nèi)存機(jī)制。)
【圖片缺失,請(qǐng)查看文末的原文鏈接,原文中圖片正常】
在每一次執(zhí)行完fun()之后,fun()函數(shù)的執(zhí)行環(huán)境被釋放(回收機(jī)制)。
接下來我們來看一個(gè)閉包:
function fun(a) { return function(b){return a + b;} } var add = fun(2); alert(add(2)); alert(add(4));
上面就出現(xiàn)閉包了,注意,我們這里出現(xiàn)了一個(gè)add變量。
【圖片缺失,請(qǐng)查看文末的原文鏈接,原文中圖片正常】
在后兩步中,實(shí)際上fun(2)部分沒有任何變化,所變的,則是在內(nèi)部函數(shù)所對(duì)應(yīng)的內(nèi)存區(qū)域中有變化。細(xì)心的你,可能會(huì)發(fā)現(xiàn)這里面的問題所在,當(dāng)執(zhí)行完add(2)之后,fun對(duì)應(yīng)的內(nèi)存沒有被釋放掉,而它的內(nèi)部函數(shù),也就是function(2)被釋放掉了,在執(zhí)行add(4)的時(shí)候,僅僅重新運(yùn)行了內(nèi)部函數(shù)。如果連fun()對(duì)應(yīng)的內(nèi)存出現(xiàn)了變化怎么辦?我們來看下面的例子:
function fun() { var b = 2; return function(a){ return a + ++b; }; } var c = fun(); alert(c(1)); // 4 alert(c(1)); // 5
注意,這可是個(gè)非常典型的閉包的例子,它有一個(gè)局部變量b,我們來看它的內(nèi)存圖。
【圖片缺失,請(qǐng)查看文末的原文鏈接,原文中圖片正常】
注意第2、3、4步中內(nèi)存的變化。第2步時(shí),我們僅僅將fun()賦給變量c,這個(gè)時(shí)候,內(nèi)部函數(shù)function(a)并沒有被執(zhí)行,所以++b也沒有被執(zhí)行,b的值還是2。但是第3步開始,++b被先執(zhí)行,++b的意思是先自加,再進(jìn)行運(yùn)算,和b++是不同的,如果是b++,雖然c(1)的最終結(jié)果還是為4,但是在c(1)執(zhí)行開始時(shí),b應(yīng)該為2,執(zhí)行完之后才是3。
奇妙的事情發(fā)生了,在內(nèi)部函數(shù)中,++b導(dǎo)致了局部變量值發(fā)生了變化,b從2變成了3,而且,內(nèi)存并沒有被釋放,fun()的執(zhí)行環(huán)境沒有被銷毀,b還被保存在內(nèi)存中。到第4步時(shí),b的初始值是3,經(jīng)過++b 之后,變成了4。
這個(gè)內(nèi)存分析非常形象的把閉包概念中,關(guān)于“突破作用域限制”這個(gè)點(diǎn)描述的非常清楚,原本按照作用域的限制,函數(shù)的局部變量不能被外部環(huán)境訪問,更不能被修改,但是閉包卻使得外部環(huán)境不僅可以讀取到局部變量的內(nèi)容,甚至可以修改它,深入一點(diǎn)就是:閉包會(huì)導(dǎo)致閉包函數(shù)所涉及到的非自身定義的變量一直保存在內(nèi)存中,包括其父函數(shù)在內(nèi)的相關(guān)環(huán)境都不會(huì)被銷毀。
閉包到底有什么用?說了這么多,那閉包到底有什么用,我們?yōu)槭裁匆褂瞄]包呢?從上面的闡述中,你應(yīng)該已經(jīng)知道了閉包的唯一作用:突破作用域限制。那如何使用這個(gè)作用為程序服務(wù)呢?
常駐內(nèi)存,意味著讀取速度快(,當(dāng)然,內(nèi)存花銷也大,導(dǎo)致內(nèi)存溢出)。常駐內(nèi)存,意味著一旦初始化以后,就可以反復(fù)使用同一個(gè)內(nèi)存中的某個(gè)對(duì)象,而無需再次運(yùn)行程序。而這一點(diǎn),是很多插件、模塊的設(shè)計(jì)思想。
最好的例子就是上文我舉得那個(gè)define()的例子,后面用我們今天所了解的形式去實(shí)踐之后,你就會(huì)發(fā)現(xiàn)原來可以把function當(dāng)做一個(gè)其他語言中的class來對(duì)待,cat.getAge(), cat.grow()這樣的操作是不是很符合我們?cè)诰幊讨械氖褂昧?xí)慣呢?一旦一個(gè)產(chǎn)生之后,這個(gè)cat就一直在內(nèi)存中,隨時(shí)可以拿出來就用,它就是一個(gè)實(shí)例化對(duì)象。
為了更形象,我們來創(chuàng)建一個(gè)簡(jiǎn)單的代碼塊:
function Animal() { this.age = 1; this.weight = 10; return { getAge : function(){ return this.age; }, getWeight : function(){ return this.weight; }, grow : function(){ this.age ++; this.weight = this.age * 10 * 0.8; } }; } function Cat() { var cat = new Animal(); // 繼承 cat.grow = function(){ cat.age ++; cat.weight = cat.age * 10 * 0.6; } return cat; } var cat1 = new Cat(); alert(cat1.getAge()); cat1.grow(); alert(cat1.getAge());
為什么要舉這個(gè)例子呢,因?yàn)槲蚁胱屇阆胂筮@樣一種場(chǎng)景,如果沒有閉包怎么辦?
沒有閉包是這樣的一種狀態(tài):函數(shù)無法訪問自己父級(jí)(祖先,全局)對(duì)象的變量。比如:
var a = 1; function add() { return ++a; // 如果沒有閉包機(jī)制,會(huì)undefined報(bào)錯(cuò) }
這種情況怎么辦?必須以參數(shù)的形式傳入到函數(shù)中:
var a = 1; function add(a) { return ++a; } alert(add(a)); // 2
如果是這樣,就很麻煩了,你需要在每一個(gè)函數(shù)中傳入變量。而更麻煩的是,沒有了作用域的突破,例如:
function Cat() { age = 1; function getAge(age) { return age; } function grow(age) { age ++; } return { getAge : getAge, grow : grow } } var cat = new Cat(); cat.grow(); alert(cat.getAge()); // 1,沒有被修改
這種情況下,我們無論如何都無法使用這種辦法來實(shí)現(xiàn)我們的目的。唯一能夠?qū)崿F(xiàn)的,就是按照下面這種方法:
var cat = { age : 1, weight : 10, grow : function(){ this.age ++; this.weight += 3; } }; var cat1 = cat; alert(cat1.age); cat1.grow(); alert(cat1.age);
我們聰明的使用到了this關(guān)鍵字,但是這樣一個(gè)壞處是,age, weight這些屬性直接暴露給外部,我們只需要執(zhí)行 cat1.age = 12; 就可以馬上讓cat長(zhǎng)到12歲,而體重卻沒任何變化。
總結(jié)而言,閉包可以帶來這么幾個(gè)方面的應(yīng)用優(yōu)勢(shì):
常駐內(nèi)存,加快運(yùn)行速度
封裝
閉包使用中的注意點(diǎn)除了上面提到的內(nèi)存開銷問題外,還有一個(gè)需要被注意的地方,就是閉包所引用的外部變量,在一些特殊情況下會(huì)存在與期望值不同的誤差。
function init() { var pAry = document.getElementsByTagName("p"); for( var i=0; i在上面這段代碼中,你希望通過一個(gè)循環(huán),來為每一個(gè)p標(biāo)簽綁定一個(gè)click事件,然而不幸的是,for循環(huán)中使用的閉包函數(shù)沒有讓你如愿以償,在閉包函數(shù)中,i被認(rèn)作pAry.length,也就是循環(huán)到最后i的最終值。為什么會(huì)這樣呢? 原來,閉包引用變量,而非直接使用變量,“引用”的意思是將指針指向變量的內(nèi)容。由于這個(gè)原因,當(dāng)i=0的時(shí)候,閉包里面的i確實(shí)是0,但是當(dāng)隨著i的值變大的時(shí)候,閉包內(nèi)的i并沒有保存當(dāng)前值,而是繼續(xù)把指針指向i的內(nèi)容,當(dāng)你點(diǎn)擊某個(gè)p標(biāo)簽的時(shí)候,i的內(nèi)容實(shí)際上是for到最后i的值。
同樣,這個(gè)問題會(huì)出現(xiàn)在setTimeout,setInterval,$.ajax等這類操作中,你只要記住,當(dāng)你綁定操作時(shí),和執(zhí)行操作時(shí),對(duì)應(yīng)的變量是否已經(jīng)變化就OK了。
原文鏈接:http://www.tangshuang.net/2368.html
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/87745.html
摘要:大名鼎鼎的閉包面試必問。閉包的作用是什么。看到閉包在哪了嗎閉包到底是什么五年前,我也被這個(gè)問題困擾,于是去搜了并總結(jié)下來。關(guān)于閉包的謠言閉包會(huì)造成內(nèi)存泄露錯(cuò)。閉包里面的變量明明就是我們需要的變量,憑什么說是內(nèi)存泄露這個(gè)謠言是如何來的因?yàn)椤? 本文為饑人谷講師方方原創(chuàng)文章,首發(fā)于 前端學(xué)習(xí)指南。 大名鼎鼎的閉包!面試必問。請(qǐng)用自己的話簡(jiǎn)述 什么是「閉包」。 「閉包」的作用是什么。 首先...
摘要:如果你想了解更多關(guān)于強(qiáng)制類型轉(zhuǎn)換的信息,你可以讀一讀的這篇文章。在只使用的情況下,所帶來的強(qiáng)制類型轉(zhuǎn)換使得判斷結(jié)果跟蹤變得復(fù)雜,下面的例子可以看出這樣的結(jié)果有多怪了明智地使用真假判斷當(dāng)我們?cè)谝粋€(gè)條件語句中使用變量或表達(dá)式時(shí),會(huì)做真假判斷。 說明 如果本文檔中有任何錯(cuò)誤的、不符合行規(guī)的,敬請(qǐng)斧正。 引言 不管有多少人共同參與同一項(xiàng)目,一定要確保每一行代碼都像是同一個(gè)人編寫的。...
摘要:盡可能的使用局部變量,少用全局變量。正確的實(shí)現(xiàn)就是在函數(shù)體內(nèi)部使用將聲明成局部變量。在新特性中,引入了塊級(jí)作用域這個(gè)概念,因此還可以使用,來聲明局部變量。它們共享外部變量,并且閉包還可以更新的值。 變量作用域 作用域,對(duì)于JavaScript語言來說無處不在,變量作用域,函數(shù)作用域(運(yùn)行時(shí)上下文和定義時(shí)上下文),作用域污染等等都跟作用域息息相關(guān),掌握J(rèn)avaScript作用于規(guī)則,可以...
摘要:在構(gòu)造函數(shù)的內(nèi)部,的指向是新創(chuàng)建的對(duì)象。如果構(gòu)造函數(shù)沒有顯式的表達(dá)式,則會(huì)隱式的返回新創(chuàng)建的對(duì)象對(duì)象。原型模式在構(gòu)造函數(shù)模式中提到每次之后創(chuàng)建的新的對(duì)象是互相獨(dú)立的,是獨(dú)享的。 1.構(gòu)造函數(shù)模式 JavaScript中的構(gòu)造函數(shù)是通過new調(diào)用的,也就是說,通過new關(guān)鍵字調(diào)用的函數(shù)都被認(rèn)為是構(gòu)造函數(shù)。 在構(gòu)造函數(shù)的內(nèi)部,this的指向是新創(chuàng)建的對(duì)象Object。 如果構(gòu)造函數(shù)沒有顯式...
閱讀 1535·2023-04-26 02:50
閱讀 3535·2023-04-26 00:28
閱讀 1931·2023-04-25 15:18
閱讀 3209·2021-11-24 10:31
閱讀 986·2019-08-30 13:00
閱讀 1000·2019-08-29 15:19
閱讀 1766·2019-08-29 13:09
閱讀 2975·2019-08-29 13:06