摘要:當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域,但是閉包情況有所不同。閉包與變量副作用閉包只能取得外層函數(shù)中任何變量的最后一個(gè)值??梢栽L問變量,因?yàn)檫@個(gè)匿名函數(shù)時(shí)一個(gè)閉包,它能夠訪問包含作用域中的所有變量。
*前言:這次總結(jié)閉包,分別參考了《js高級(jí)程序設(shè)計(jì)》、廖雪峰老師的網(wǎng)站、還有《js忍著秘籍》,好了,廢話少說,黑喂狗~~~
---------------------嚴(yán)肅分割線-------------------*
1.js函數(shù)中的作用域鏈沒錯(cuò),閉包還是要從作用域鏈說起,要理解閉包必須從函數(shù)第一次被調(diào)用時(shí)發(fā)生了什么入手,先看一個(gè)例子,代碼:
function compare(value1,value2){ if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } var result = compare(5,10); //全局作用域中調(diào)用
首先定義了一個(gè)compare函數(shù),然后又在全局作用域中調(diào)用了它。當(dāng)調(diào)用compare()時(shí),會(huì)創(chuàng)建一個(gè)包含(this,arguments,value1,value2)的活動(dòng)對(duì)象,而全局執(zhí)行環(huán)境的變量對(duì)象(this,result,compare)在compare()執(zhí)行環(huán)境的作用域鏈中處于第二位。在這例子中,當(dāng)調(diào)用compare()函數(shù)時(shí),會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境,其作用域鏈包含兩個(gè)變量對(duì)象:本地活動(dòng)對(duì)象和全局對(duì)象,在函數(shù)中訪問一個(gè)變量時(shí),會(huì)先從本地作用域中查找,找不到則向上查找外層函數(shù)的作用域中是否有,直到全局作用域。當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域,但是閉包情況有所不同。
2.閉包中的作用域鏈先看一個(gè)例子:
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } }; } var compareNames = createComparisonFunction("name"); var result = compareNames({name:"Jack"},{name:"Rose"}); compareNames = null; //銷毀匿名函數(shù)
上面的例子中,在createComparisonFunction函數(shù)內(nèi)部定義了一個(gè)匿名函數(shù),它的作用域鏈包含外部函數(shù)createComparisonFunction()的活動(dòng)對(duì)象和全局變量對(duì)象,所以匿名函數(shù)中可以訪問createComparisonFunction()函數(shù)中的propertyName。
最重要的是:createComparisonFunction()函數(shù)在執(zhí)行完畢后,它的執(zhí)行環(huán)境作用域鏈會(huì)被銷毀,其活動(dòng)對(duì)象仍然會(huì)留在內(nèi)存中,這就是為什么上面的代碼中,執(zhí)行:
var compare = createComparisonFunction("name")之后,createComparisonFunction()函數(shù)已經(jīng)執(zhí)行完畢,但是仍然可以在下一行代碼中執(zhí)行比較大小的操作。
創(chuàng)建的比較函數(shù)被保存在變量compareNames中,設(shè)置它等于null,解除該函數(shù)引用,垃圾回收。
閉包只能取得外層函數(shù)中任何變量的最后一個(gè)值??聪旅胬樱?/p>
function createFunctions(){ var arr = new Array(); for(var i=0; i<10;i++){ arr[i] = function(){ return i; }; } return arr; } var result = createFunctions(); var f1 = result[0]; var f2 = result[1]; var f3 = result[2]; //執(zhí)行函數(shù) alert(f1()); //10 alert(f2()); //10 alert(f3()); //10
這個(gè)例子中似乎每個(gè)函數(shù)都應(yīng)該返回自己的索引值,實(shí)際上每個(gè)匿名函數(shù)返回的都是10。因?yàn)槊總€(gè)匿名函數(shù)中都保存著createFunctions()函數(shù)的活動(dòng)對(duì)象,所以它們引用的是同一個(gè)變量i,函數(shù)createFunctions()返回后,變量i的值都是10,此時(shí)每個(gè)函數(shù)都引用著保存變量i的同一個(gè)變量對(duì)象,所以每個(gè)函數(shù)內(nèi)部i的值都是10.
注意:上述函數(shù)的調(diào)用過程:執(zhí)行createFunctions()函數(shù),并且把函數(shù)執(zhí)行結(jié)果賦值給變量result,現(xiàn)在result是一個(gè)數(shù)組,再把result[0]賦值給f1,f1實(shí)際上代表的是內(nèi)部的匿名函數(shù),現(xiàn)在執(zhí)行這個(gè)函數(shù):f1(),就會(huì)得到i的值。
改造可以通過創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期,看下面例子:
function createFunctions(){ var result = new Array(); for(var i = 0; i < 10; i++){ result[i] = (function(num){ return function(){ return num; }; })(i); } return result; } var final = createFunctions(); var f1 = final[0]; alert(f1()); //0
這次沒有直接把閉包賦值給數(shù)組,而是定義了一個(gè)匿名函數(shù),并立即執(zhí)行該函數(shù)的結(jié)果賦值給數(shù)組。這個(gè)匿名函數(shù)有一個(gè)參數(shù)num,也就是最終要返回的值,調(diào)用這個(gè)匿名函數(shù)時(shí),傳入了變量i,由于函數(shù)參數(shù)是按值傳遞的,所以會(huì)把變量i的當(dāng)前值傳遞給num,這個(gè)匿名函數(shù)內(nèi)部,又創(chuàng)建并返回了一個(gè)訪問num的閉包,這樣,最里面的匿名函數(shù)會(huì)鎖住外層匿名函數(shù)傳遞進(jìn)來的num值即當(dāng)前i的值,并且返回num,這樣num就會(huì)等于此時(shí)的i的值并且賦值給數(shù)組。
4.閉包就是匿名函數(shù)嗎?上述代碼中很容易讓人誤解:閉包就是一個(gè)匿名函數(shù),其實(shí)不然,看下面例子:
var outName = "外面的名字"; var later; function outerFunction(){ var innerName = "里面的名字"; function innerFunction(){ alert("I can see the "+outName); alert("I can see the "+innerName); } later = innerFunction; } outerFunction(); later();
上述代碼中,在outerFunction()函數(shù)中,將內(nèi)部函數(shù)innerFunction()賦值給全局變量later,執(zhí)行完倒數(shù)第二步:outerFunction();之后,執(zhí)行later();依然可以可以訪問到內(nèi)部變量innerName。
因?yàn)樵谕獠亢瘮?shù)outerFunction中聲明innerFunction()函數(shù)時(shí),不僅聲明了函數(shù),還創(chuàng)建了一個(gè)閉包,該閉包不僅包含函數(shù)聲明,還包含了函數(shù)聲明的那一時(shí)刻該作用域中的所有變量。
上述例子可以看出,閉包可以用來封裝私有變量,就像java中的在對(duì)象內(nèi)部封裝一個(gè)private私有變量一樣。
看下面例子:
function createCounter(){ var num = 0; this.getNum = function(){ return num; }; this.num = function(){ num++; }; } var counter = new createCounter(); counter.num() //調(diào)用計(jì)數(shù)器一次,使num值加1 //測(cè)試代碼 alert(counter.getNum()); //1 alert(counter.num()); //undefined
上面例子中用構(gòu)造函數(shù)模式創(chuàng)建了一個(gè)計(jì)數(shù)器函數(shù),然后對(duì)函數(shù)進(jìn)行實(shí)例化,在構(gòu)造函數(shù)內(nèi)部,我們定義了一個(gè)變量num,它的可訪問性只能在構(gòu)造器內(nèi)部,定義了一個(gè)getNum()方法,該方法只能對(duì)內(nèi)部變量進(jìn)行讀取,但不能寫入。然后又定義了方法num(),通過最后兩行測(cè)試代碼可以看出,可以通過存取方法getNum獲取私有變量,但是不能直接訪問私有變量。
總結(jié):私有變量的意思是,我們?nèi)绻雽?duì)num進(jìn)行加減乘除的操作,只能在createCounter內(nèi)部,外部只能訪問內(nèi)部進(jìn)行邏輯操作后的值,而不能訪問帶有對(duì)num值進(jìn)行操作的方法,這樣,我們就可以把自己的業(yè)務(wù)邏輯封裝在函數(shù)內(nèi)部的閉包中,只需要暴露出接口讓外部獲取想要得到的值就可以了,也就是說主動(dòng)權(quán)完全在你定義函數(shù)時(shí),外部只能看和獲取,而不能進(jìn)行對(duì)變量值的改變的操作。
上述的目的就是創(chuàng)建一個(gè)用于訪問私有變量的公有方法??聪旅娲a:
function Person(name){ this.getName = function(){ return name; }; this.setName = function(){ name = value; }; } //測(cè)試 var person = new Person("Jack"); alert(person.getName()); //Jack person.setName("Rose"); alert(person.getName()); //Rose
上面的代碼在構(gòu)造函數(shù)內(nèi)部定義了兩個(gè)方法:setName和getName,這兩個(gè)方法都可以在構(gòu)造函數(shù)外部訪問和實(shí)用,而且都有權(quán)訪問私有變量name,但在Person構(gòu)造函數(shù)外部,沒有任何辦法訪問name,由于這兩個(gè)方法是在構(gòu)造函數(shù)內(nèi)部定義的,所以做為閉包能夠通過作用域鏈訪問name。上述在構(gòu)造函數(shù)中定義特權(quán)方法有一個(gè)缺點(diǎn),就是必須要使用構(gòu)造函數(shù)模式來達(dá)到這個(gè)目的,這樣針對(duì)每個(gè)實(shí)例都會(huì)創(chuàng)建同樣一組新方法。
解決辦法:靜態(tài)私有變量
看下面代碼:
(function(){ var name = ""; Person = function(value){ name = value; }; Person.prototype.getName = function(){ return name; }; Person.prototype.setName = function(value){ name = value; }; })(); //測(cè)試函數(shù) var person1 = new Person("Jack"); alert(person1.getName()); //Jack person1.setName("Rose"); alert(person1.getName()); //Rose var person2 = new Person("Will"); alert(person1.getName()); //Will alert(person2.getName()); //Will
上述代碼中,Person構(gòu)造函數(shù)與getName()和setName()方法一樣,都有權(quán)訪問私有變量name,name變成了一個(gè)靜態(tài)的、由所有實(shí)例共享的屬性。在一個(gè)實(shí)例上調(diào)用setName會(huì)影響所有的實(shí)例?;蛘咝陆ㄒ粋€(gè)Person實(shí)例都會(huì)賦予name屬性一個(gè)新值。
上述兩個(gè)方法:第二種創(chuàng)建靜態(tài)私有變量會(huì)因?yàn)槭褂迷投鲞M(jìn)代碼復(fù)用,但每個(gè)實(shí)例都沒有自己的私有變量。
模仿塊級(jí)作用域上面舉過例子,js沒有塊級(jí)作用域,可以通過匿名函數(shù)立即執(zhí)行把塊級(jí)作用域包裹起來,這樣就有了塊級(jí)作用域。看下面代碼:
function outputNumbers(count){ (function(){ for(var i = 0; i上述函數(shù)中,在for循環(huán)外部插入了一個(gè)私有作用域,在匿名函數(shù)中定義的任何變量,都會(huì)在匿名函數(shù)執(zhí)行結(jié)束時(shí)被銷毀。因此變量i只能在for循環(huán)中使用,使用后即被銷毀,因此上面的代碼執(zhí)行會(huì)這樣:
1.執(zhí)行測(cè)試函數(shù)后,會(huì)彈出5個(gè)彈窗,會(huì)顯示0,1,2,3,4
2.執(zhí)行完匿名函數(shù)后,i即被銷毀,所以執(zhí)行alert(i);會(huì)報(bào)錯(cuò)。
3.可以訪問變量count,因?yàn)檫@個(gè)匿名函數(shù)時(shí)一個(gè)閉包,它能夠訪問包含作用域中的所有變量。這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部,從而限制向全局作用域中添加過多的變量和函數(shù),看下面代碼:
(function(){ var now = new Date(); if(now.getMonth() == 0 && now.getDate() == 1){ alert("元旦快樂"); } })();上述代碼放在全局作用域中,可以用來確定哪一天時(shí)1月1日元旦,now是匿名函數(shù)中的局部變量,不用在全局作用域中創(chuàng)建它。
先寫這么多吧,以后再添加~~~~~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79707.html
摘要:一言以蔽之,閉包,你就得掌握。當(dāng)函數(shù)記住并訪問所在的詞法作用域,閉包就產(chǎn)生了。所以閉包才會(huì)得以實(shí)現(xiàn)。從技術(shù)上講,這就是閉包。執(zhí)行后,他的內(nèi)部作用域并不會(huì)消失,函數(shù)依然保持有作用域的閉包。 網(wǎng)上總結(jié)閉包的文章已經(jīng)爛大街了,不敢說筆者這篇文章多么多么xxx,只是個(gè)人理解總結(jié)。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結(jié)與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:閉包引起的內(nèi)存泄漏總結(jié)從理論的角度將由于作用域鏈的特性中所有函數(shù)都是閉包但是從應(yīng)用的角度來說只有當(dāng)函數(shù)以返回值返回或者當(dāng)函數(shù)以參數(shù)形式使用或者當(dāng)函數(shù)中自由變量在函數(shù)外被引用時(shí)才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會(huì)問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經(jīng)典書籍等書中給出的概念,這些概念雖然...
摘要:插件開發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。插....
摘要:譯者按在上一篇博客,我們通過實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,了解了如何使用閉包,這篇博客將提供一些代碼示例,幫助大家理解閉包。然而,如果通過代碼示例去理解閉包,則簡(jiǎn)單很多。不過,將閉包簡(jiǎn)單地看做局部變量,理解起來會(huì)更加簡(jiǎn)單。 - 譯者按: 在上一篇博客,我們通過實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,了解了如何使用閉包(Closure),這篇博客將提供一些代碼示例,幫助大家理解閉包。 原文: JavaScript Clos...
摘要:一前言這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來,算是鞏固自己所學(xué)。因此要注意閉包的使用,否則會(huì)導(dǎo)致性能問題。五總結(jié)閉包的作用能夠讀取其他函數(shù)內(nèi)部變量。 # 一、前言 這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)Js知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來,算是鞏固自己所學(xué)。也可能有些不正確的地方,也請(qǐng)大家看到了,麻...
閱讀 972·2021-11-23 09:51
閱讀 1031·2021-11-18 10:02
閱讀 1982·2021-09-10 11:27
閱讀 3171·2021-09-10 10:51
閱讀 804·2019-08-29 15:13
閱讀 2093·2019-08-29 11:32
閱讀 2525·2019-08-29 11:25
閱讀 3068·2019-08-26 11:46