JavaScript 匿名函數(shù)(anonymous function)與閉包(closure)
引入
匿名函數(shù)
閉包
變量作用域
函數(shù)外部訪問函數(shù)內(nèi)部的局部變量
用閉包實(shí)現(xiàn)私有成員
引入
閉包是用匿名函數(shù)來實(shí)現(xiàn)。閉包就是一個(gè)受到保護(hù)的變量空間,由內(nèi)嵌函數(shù)生成。“保護(hù)變量”的思想在幾乎所有的編程語言中都能看到。
先看下 JavaScript 作用域:
JavaScript 具有函數(shù)級的作用域。這意味著,不能在函數(shù)外部訪問定義在函數(shù)內(nèi)部的變量。
JavaScript 的作用域又是詞法性質(zhì)的(lexically scoped)。這意味著,函數(shù)運(yùn)行在定義它的作用域中,而不是在調(diào)用它的作用域中。這是 JavaScript 的一大特色,將在后面說明。
把這兩個(gè)因素結(jié)合在一起,就能通過把變量包裹在匿名函數(shù)中而對其加以保護(hù)。你可以這樣創(chuàng)建類的私有變量:
var baz; (function() { var foo = 10; var bar = 2; baz = function() { return foo * bar; }; })(); baz();
盡管在匿名函數(shù)外執(zhí)行,但 baz 仍然可以訪問 foo 和 bar。
說明:
1,第 1 行,baz 是全局變量;
2,第 3 ~第 9 行,定義一個(gè)匿名函數(shù);
3,第 4 和 5 行,foo 和 bar 是匿名函數(shù)內(nèi)的局部變量;第 6 ~ 8 行,在匿名函數(shù)內(nèi)定義一個(gè)匿名函數(shù),并將其賦值給全局變量 baz;
4,第 10 行,調(diào)用 baz。若改成 "alert(baz());",將顯示 20;
5,按理說,在匿名函數(shù)外不能訪問 foo 和 bar,但是現(xiàn)在可以。
在說明閉包前,先了解一下匿名函數(shù)。
匿名函數(shù)
匿名函數(shù)是指那些無需定義函數(shù)名的函數(shù)。匿名函數(shù)與 Lambda 表達(dá)式(拉姆達(dá)表達(dá)式)是一回事。唯一的不同——語法形式不同。Lambda 表達(dá)式更進(jìn)一步。本質(zhì)上,它們的作用都是:產(chǎn)生方法——內(nèi)聯(lián)方法,也就是說,省去函數(shù)定義,直接寫函數(shù)體。
Lambda 表達(dá)式一般形式:
(input parameters) => {statement;}
其中:
參數(shù)列表,可以有多個(gè)、一個(gè)或者無參數(shù)。參數(shù)可以隱式或者顯式定義。
表達(dá)式或者語句塊,也就是函數(shù)體。
上面代碼,第 6 ~ 8 行,沒有函數(shù)名,是個(gè)匿名函數(shù),采用 Lambda 表達(dá)式,嚴(yán)格意義上,雖然語法有差異,但目的一樣。
示例1:
var baz1 = function() { var foo = 10; var bar = 2; return foo * bar; }; function mutil() { var foo = 10; var bar = 2; return foo * bar; }; alert(baz1()); var baz2 = mutil(); alert(baz2);
說明:
1,baz1 與 baz2 完全一樣,但 baz1 與 baz2 相比,省去了函數(shù)定義,直接函數(shù)體——看上去多簡約。
閉包
變量作用域
示例2:函數(shù)內(nèi)部可以訪問全局變量。
var baz = 10; function foo() { alert(baz); } foo();
這是可以。
示例3:函數(shù)外部不能訪問函數(shù)內(nèi)部的局部變量。
function foo() { var bar = 20; } alert(bar);
這會(huì)報(bào)錯(cuò)。
另外,函數(shù)內(nèi)部聲明變量時(shí),一定要使用 var 關(guān)鍵字,否則,聲明的是一個(gè)全局變量。
示例4:
function foo() { bar = 20; } alert(bar);
函數(shù)外部訪問函數(shù)內(nèi)部的局部變量
實(shí)際情況,需要我們從函數(shù)外部獲得函數(shù)內(nèi)部的局部變量。先看示例5。
示例5:
function foo() { var a = 10; function bar() { a *= 2; } bar(); return a; } var baz = foo(); alert(baz);
a 定義在 foo 內(nèi),bar 可以訪問,因?yàn)?bar 也定義在 foo 內(nèi)?,F(xiàn)在,如何讓 bar 在 foo 外部被調(diào)用?
示例6:
function foo() { var a = 10; function bar() { a *= 2; return a; } return bar; } var baz = foo(); alert(baz()); alert(baz()); alert(baz()); var blat = foo(); alert(blat());
說明:
1,現(xiàn)在可以從外部訪問 a;
2,JavaScript 的作用域是詞法性的。a 是運(yùn)行在定義它的 foo 中,而不是運(yùn)行在調(diào)用 foo 的作用域中。 只要 bar 被定義在 foo 中,它就能訪問 foo 中定義的變量 a,即使 foo 的執(zhí)行已經(jīng)結(jié)束。也就是說,按理,"var baz = foo()" 執(zhí)行后,foo 已經(jīng)執(zhí)行結(jié)束,a 應(yīng)該不存在了,但之后再調(diào)用 baz 發(fā)現(xiàn),a 依然存在。這就是 JavaScript 特色之一——運(yùn)行在定義,而不是運(yùn)行的調(diào)用。
其中, "var baz = foo()" 是一個(gè) bar 函數(shù)的引用;"var blat= foo()" 是另一個(gè) bar 函數(shù)引用。
用閉包實(shí)現(xiàn)私有成員
現(xiàn)在,需要?jiǎng)?chuàng)建一個(gè)只能在對象內(nèi)部訪問的變量。用閉包再適合不過,因?yàn)橥ㄟ^閉包你可以創(chuàng)建只允許特定函數(shù)訪問的變量,而且這些變量在這些函數(shù)的各次調(diào)用間依然存在。
為了創(chuàng)建私有屬性,你需要在構(gòu)造函數(shù)的作用域中定義相關(guān)變量。這些變量可以被定義于該作用域中的所有函數(shù)訪問,包括那些特權(quán)方法。
示例7:
var Book = function(newIsbn, newTitle, newAuthor) { // 私有屬性 var isbn, title, author; // 私有方法 function checkIsbn(isbn) { // TODO } // 特權(quán)方法 this.getIsbn = function() { return isbn; }; this.setIsbn = function(newIsbn) { if (!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); isbn = newIsbn; }; this.getTitle = function() { return title; }; this.setTitle = function(newTitle) { title = newTitle || 'No title specified.'; }; this.getAuthor = function() { return author; }; this.setAuthor = function(newAuthor) { author = newAuthor || 'No author specified.'; }; // 構(gòu)造器代碼 this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); }; // 共有、非特權(quán)方法 Book.prototype = { display: function() { // TODO } };
說明:
1,用 var 聲明變量 isbn、title 和 author,而不是 this,意味著它們只存在 Book 構(gòu)造器中。checkIsbn 函數(shù)也是,因?yàn)樗鼈兪撬接械模?
2,訪問私有變量和方法的方法只需聲明在 Book 中即可。這些方法稱為特權(quán)方法。因?yàn)椋鼈兪枪卜椒?,但卻能訪問私有變量和私有方法,像 getIsbn、setIsbn、getTitle、setTitle、getAuthor、setAuthor(取值器和構(gòu)造器)。
3,為了能在對象外部訪問這些特權(quán)方法,這些方法前邊加了 this 關(guān)鍵字。因?yàn)檫@些方法定義在 Book 構(gòu)造器的作用域里,所以它們能夠訪問私有變量 isbn、title 和 author。但在這些特權(quán)方法里引用 isbn、title 和 author 變量時(shí),沒有使用 this 關(guān)鍵字,而是直接引用。因?yàn)樗鼈儾皇枪_的。
4,任何不需要直接訪問私有變量的方法,像 Book.prototype 中聲明的,如 display。它不需要直接訪問私有變量,而是通過 get*、set* 簡介訪問。
5,這種方式創(chuàng)建的對象可以具有真正私有的變量。其他人不能直接訪問 Book 對象的任何內(nèi)部數(shù)據(jù),只能通過賦值器和。這樣一切盡在掌握。
但這種方式的缺點(diǎn)是:
“門戶大開型”對象創(chuàng)建模式中,所有方法都創(chuàng)建在原型 prototype 對象中,因此不管生成多少對象實(shí)例,這些方法在內(nèi)存中只有一份。
而采用本節(jié)的做法,沒生成一個(gè)新的對象實(shí)例,都將為每個(gè)私有方法(如,checkIsbn)和特權(quán)方法(如,getIsbn、setIsbn、getTitle、setTitle、getAuthor、setAuthor)生成一個(gè)新的副本。
因此,本節(jié)方法,只適于用在真正需要私有成員的場合。另外,這種方式也不利于繼承。
相關(guān)文章
JS在Chrome瀏覽器中showModalDialog函數(shù)返回值為undefined的解決方法
這篇文章主要介紹了JS在Chrome瀏覽器中showModalDialog函數(shù)返回值為undefined的解決方法,涉及javascript針對谷歌瀏覽器事件判定相關(guān)操作技巧,需要的朋友可以參考下2016-08-08jquery實(shí)現(xiàn)select下拉框美化特效代碼分享
這篇文章主要介紹了jquery實(shí)現(xiàn)select下拉框美化特效,實(shí)現(xiàn)效果簡潔大方,推薦給大家,有需要的小伙伴可以參考下。2015-08-08javascript forEach函數(shù)實(shí)現(xiàn)代碼
在Base2中找到一個(gè)叫forEach的函數(shù),是我見過的最好的實(shí)現(xiàn)。挖出來分析一下。它能對各種普通對象,字符串,數(shù)組以及類數(shù)組進(jìn)行遍歷。如果原游覽器的對象已實(shí)現(xiàn)此函數(shù),它則調(diào)用原對象的函數(shù)。2010-01-01Net微信網(wǎng)頁開發(fā) 使用微信JS-SDK獲取當(dāng)前地理位置過程詳解
這篇文章主要介紹了Net微信網(wǎng)頁開發(fā) 使用微信JS-SDK獲取當(dāng)前地理位置過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08