15.javascript 匿名函數(shù)和閉包

學(xué)習(xí)要點(diǎn):

1.匿名函數(shù)
2.閉包

匿名函數(shù)就是沒有名字的函數(shù)驶睦,閉包是可訪問一個(gè)函數(shù)作用域里變量的函數(shù)卑惜。聲明:本
節(jié)內(nèi)容需要有面向?qū)ο蠛蜕倭吭O(shè)計(jì)模式基礎(chǔ),否則無法聽懂 .(所需基礎(chǔ) 15 章的時(shí)候已經(jīng)聲
明過了)。

一.匿名函數(shù)

//普通函數(shù)
function box() { //函數(shù)名是 box
return 'Lee';
}
//匿名函數(shù)
function () { //匿名函數(shù)痊乾,會(huì)報(bào)錯(cuò)
return 'Lee';
}
//通過表達(dá)式自我執(zhí)行
(function box() { //封裝成表達(dá)式
alert('Lee');
})(); //()表示執(zhí)行函數(shù),并且傳參
//把匿名函數(shù)賦值給變量
var box = function () { //將匿名函數(shù)賦給變量
return 'Lee';
};
alert(box()); //調(diào)用方式和函數(shù)調(diào)用相似
//函數(shù)里的匿名函數(shù)
function box () {
return function () { //函數(shù)里的匿名函數(shù)椭更,產(chǎn)生閉包
return 'Lee';
}
}
alert(box()()); //調(diào)用匿名函數(shù)

二.閉包

閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)哪审,創(chuàng)建閉包的常見的方式,就是在
一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)虑瀑,通過另一個(gè)函數(shù)訪問這個(gè)函數(shù)的局部變量湿滓。

//通過閉包可以返回局部變量
function box() {
var user = 'Lee';
return function () { //通過匿名函數(shù)返回 box()局部變量
return user;
};
}
alert(box()()); //通過 box()()來直接調(diào)用匿名函數(shù)返回值
var b = box();
alert(b()); //另一種調(diào)用匿名函數(shù)返回值

使用閉包有一個(gè)優(yōu)點(diǎn),也是它的缺點(diǎn):就是可以把局部變量駐留在內(nèi)存中舌狗,可以避免使
用全局變量叽奥。(全局變量污染導(dǎo)致應(yīng)用程序不可預(yù)測性,每個(gè)模塊都可調(diào)用必將引來災(zāi)難痛侍,
所以推薦使用私有的朝氓,封裝的局部變量)。

//通過全局變量來累加
var age = 100; //全局變量
function box() {
age ++; //模塊級(jí)可以調(diào)用全局變量主届,進(jìn)行累加
}
box(); //執(zhí)行函數(shù)赵哲,累加了
alert(age); //輸出全局變量
//通過局部變量無法實(shí)現(xiàn)累加
function box() {
var age = 100;
age ++; //累加
return age;
}
alert(box()); //101
alert(box()); //101,無法實(shí)現(xiàn)君丁,因?yàn)橛直怀跏蓟?//通過閉包可以實(shí)現(xiàn)局部變量的累加
function box() {
var age = 100;
return function () {
age ++;
return age;
}
}
var b = box(); //獲得函數(shù)
alert(b()); //調(diào)用匿名函數(shù)
alert(b()); //第二次調(diào)用匿名函數(shù)枫夺,實(shí)現(xiàn)累加

PS:由于閉包里作用域返回的局部變量資源不會(huì)被立刻銷毀回收,所以可能會(huì)占用更
多的內(nèi)存绘闷。過度使用閉包會(huì)導(dǎo)致性能下降橡庞,建議在非常有必要的時(shí)候才使用閉包。
作用域鏈的機(jī)制導(dǎo)致一個(gè)問題簸喂,在循環(huán)中里的匿名函數(shù)取得的任何變量都是最后一個(gè)
值毙死。

//循環(huán)里包含匿名函數(shù)
function box() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
return arr;
}
var b = box(); //得到函數(shù)數(shù)組
alert(b.length); //得到函數(shù)集合長度
for (var i = 0; i < b.length; i++) {
alert(b[i]()); //輸出每個(gè)函數(shù)的值,都是最后一個(gè)值
}

上面的例子輸出的結(jié)果都是 5喻鳄,也就是循環(huán)后得到的最大的 i 值扼倘。因?yàn)?b[i]調(diào)用的是匿
名函數(shù),匿名函數(shù)并沒有自我執(zhí)行,等到調(diào)用的時(shí)候再菊,box()已執(zhí)行完畢爪喘,i 早已變成 5,所
以最終的結(jié)果就是 5 個(gè) 5

//循環(huán)里包含匿名函數(shù)-改 1纠拔,自我執(zhí)行匿名函數(shù)
function box() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = (function (num) { //自我執(zhí)行
return num;
})(i); //并且傳參
}
return arr;
}
var b = box();
for (var i = 0; i < b.length; i++) {
alert(b[i]); //這里返回的是數(shù)組秉剑,直接打印即可
}

改 1 中稠诲,我們讓匿名函數(shù)進(jìn)行自我執(zhí)行侦鹏,導(dǎo)致最終返回給 a[i]的是數(shù)組而不是函數(shù)了。
最終導(dǎo)致 b[0]-b[4]中保留了 0,1,2,3,4 的值臀叙。

//循環(huán)里包含匿名函數(shù)-改 2略水,匿名函數(shù)下再做個(gè)匿名函數(shù)
function box() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = (function (num) {
return function () { //直接返回值,改 2 變成返回函數(shù)
return num; //原理和改 1 一樣
}
})(i);
}
return arr;
}
var b = box();
for (var i = 0; i < b.length; i++) {
alert(b[i]()); //這里通過 b[i]()函數(shù)調(diào)用即可
}

改 1 和改 2 中劝萤,我們通過匿名函數(shù)自我執(zhí)行渊涝,立即把結(jié)果賦值給 a[i]。每一個(gè) i床嫌,是調(diào)
用方通過按值傳遞的跨释,所以最終返回的都是指定的遞增的 i。而不是 box()函數(shù)里的 i厌处。
關(guān)于 this 對象
在閉包中使用 this 對象也可能會(huì)導(dǎo)致一些問題鳖谈,this 對象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)
境綁定的,如果 this 在全局范圍就是 window嘱蛋,如果在對象內(nèi)部就指向這個(gè)對象蚯姆。而閉包卻
在運(yùn)行時(shí)指向 window 的,因?yàn)殚]包并不屬于這個(gè)對象的屬性或方法洒敏。

var user = 'The Window';
var obj = {
user : 'The Object',
getUserFunction : function () {
return function () { //閉包不屬于obj,里面的this指向window
return this.user;
};
}
};
alert(obj.getUserFunction()()); //The window
//可以強(qiáng)制指向某個(gè)對象
alert(obj.getUserFunction().call(obj)); //The Object
//也可以從上一個(gè)作用域中得到對象
getUserFunction : function () {
var that = this; //從對象的方法里得對象
return function () {
return that.user;
};
}

內(nèi)存泄漏由于 IE 的 JScript 對象和 DOM 對象使用不同的垃圾收集方式疙驾,因此閉包在 IE 中會(huì)導(dǎo)致
一些問題凶伙。就是內(nèi)存泄漏的問題,也就是無法銷毀駐留在內(nèi)存中的元素它碎。以下代碼有兩個(gè)知
識(shí)點(diǎn)還沒有學(xué)習(xí)到函荣,一個(gè)是 DOM,一個(gè)是事件扳肛。

function box() {
var oDiv = document.getElementById('oDiv'); //oDiv 用完之后一直駐留在內(nèi)存
oDiv.onclick = function () {
alert(oDiv.innerHTML); //這里用 oDiv 導(dǎo)致內(nèi)存泄漏
};
}
box();

那么在最后應(yīng)該將 oDiv 解除引用來避免內(nèi)存泄漏傻挂。

function box() {
var oDiv = document.getElementById('oDiv');
var text = oDiv.innerHTML;
oDiv.onclick = function () {
alert(text);
};
oDiv = null; //解除引用
}

PS:如果并沒有使用解除引用,那么需要等到瀏覽器關(guān)閉才得以釋放挖息。
模仿塊級(jí)作用域
JavaScript 沒有塊級(jí)作用域的概念金拒。

function box(count) {
for (var i=0; i<count; i++) {}
alert(i); //i 不會(huì)因?yàn)殡x開了 for 塊就失效
}
box(2);
function box(count) {
for (var i=0; i<count; i++) {}
var i; //就算重新聲明,也不會(huì)前面的值
alert(i);
}
box(2);

以上兩個(gè)例子,說明 JavaScript 沒有塊級(jí)語句的作用域绪抛,if () {} for () {}等沒有作用域资铡,
如果有,出了這個(gè)范圍 i 就應(yīng)該被銷毀了幢码。就算重新聲明同一個(gè)變量也不會(huì)改變它的值笤休。
JavaScript 不會(huì)提醒你是否多次聲明了同一個(gè)變量;遇到這種情況症副,它只會(huì)對后續(xù)的聲
明視而不見(如果初始化了店雅,當(dāng)然還會(huì)執(zhí)行的)。使用模仿塊級(jí)作用域可避免這個(gè)問題贞铣。

//模仿塊級(jí)作用域(私有作用域)
(function () {
//這里是塊級(jí)作用域
})();
//使用塊級(jí)作用域(私有作用域)改寫
function box(count) {
(function () {
for (var i = 0; i<count; i++) {}
})();
alert(i); //報(bào)錯(cuò)闹啦,無法訪問
}
box(2);

使用了塊級(jí)作用域(私有作用域)后,匿名函數(shù)中定義的任何變量咕娄,都會(huì)在執(zhí)行結(jié)束時(shí)被
銷毀亥揖。這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部,從而限制向全局作用域中添加過多的
變量和函數(shù)圣勒。一般來說费变,我們都應(yīng)該盡可能少向全局作用域中添加變量和函數(shù)。在大型項(xiàng)目
中圣贸,多人開發(fā)的時(shí)候挚歧,過多的全局變量和函數(shù)很容易導(dǎo)致命名沖突,引起災(zāi)難性的后果吁峻。如
果采用塊級(jí)作用域(私有作用域)滑负,每個(gè)開發(fā)者既可以使用自己的變量,又不必?fù)?dān)心搞亂全局
作用域用含。

(function () {
var box = [1,2,3,4];
alert(box); //box 出來就不認(rèn)識(shí)了
})();

在全局作用域中使用塊級(jí)作用域可以減少閉包占用的內(nèi)存問題矮慕,因?yàn)闆]有指向匿名函數(shù)
的引用。只要函數(shù)執(zhí)行完畢啄骇,就可以立即銷毀其作用域鏈了痴鳄。
私有變量
JavaScript 沒有私有屬性的概念;所有的對象屬性都是公有的缸夹。不過痪寻,卻有一個(gè)私有變
量的概念。任何在函數(shù)中定義的變量虽惭,都可以認(rèn)為是私有變量橡类,因?yàn)椴荒茉诤瘮?shù)的外部訪問
這些變量。

function box() {
var age = 100; //私有變量芽唇,外部無法訪問
}

而通過函數(shù)內(nèi)部創(chuàng)建一個(gè)閉包顾画,那么閉包通過自己的作用域鏈也可以訪問這些變量。而
利用這一點(diǎn),可以創(chuàng)建用于訪問私有變量的公有方法亲雪。

function Box() {
var age = 100; //私有變量
function run() { //私有函數(shù)
return '運(yùn)行中...';
}
this.get = function () { //對外公共的特權(quán)方法
return age + run();
};
}
var box = new Box();
alert(box.get());
可以通過構(gòu)造方法傳參來訪問私有變量勇凭。
function Person(value) {
var user = value; //這句其實(shí)可以省略
this.getUser = function () {
return user;
};
this.setUser = function (value) {
user = value;
};
}

但是對象的方法,在多次調(diào)用的時(shí)候义辕,會(huì)多次創(chuàng)建虾标。可以使用靜態(tài)私有變量來避免這個(gè)
問題灌砖。
靜態(tài)私有變量
通過塊級(jí)作用域(私有作用域)中定義私有變量或函數(shù)璧函,同樣可以創(chuàng)建對外公共的特權(quán)方
法。

(function () {
var age = 100;
function run() {
return '運(yùn)行中...';
}
Box = function () {}; //構(gòu)造方法
Box.prototype.go = function () { //原型方法
return age + run();
};
})();
var box = new Box();
alert(box.go());

上面的對象聲明基显,采用的是 Box = function () {} 而不是 function Box() {} 因?yàn)槿绻煤?br> 面這種蘸吓,就變成私有函數(shù)了,無法在全局訪問到了撩幽,所以使用了前面這種库继。

(function () {
var user = '';
Person = function (value) {
user = value;
};
Person.prototype.getUser = function () {
return user;
};
Person.prototype.setUser = function (value) {
user = value;
}
})();

使用了 prototype 導(dǎo)致方法共享了,而 user 也就變成靜態(tài)屬性了窜醉。(所謂靜態(tài)屬性宪萄,即共
享于不同對象中的屬性)。
模塊模式
之前采用的都是構(gòu)造函數(shù)的方式來創(chuàng)建私有變量和特權(quán)方法榨惰。那么對象字面量方式就采
用模塊模式來創(chuàng)建拜英。

var box = { //字面量對象,也是單例對象
age : 100, //這是公有屬性琅催,將要改成私有
run : function () { //這時(shí)公有函數(shù)居凶,將要改成私有
return '運(yùn)行中...';
};
};

私有化變量和函數(shù):

var box = function () {
var age = 100;
function run() {
return '運(yùn)行中...';
}
return { //直接返回對象
go : function () {
return age + run();
}
};
}();

上面的直接返回對象的例子,也可以這么寫:

var box = function () {
var age = 100;
function run() {
return '運(yùn)行中...';
}
var obj = { //創(chuàng)建字面量對象
go : function () {
return age + run();
}
};
return obj; //返回這個(gè)對象
}();

字面量的對象聲明藤抡,其實(shí)在設(shè)計(jì)模式中可以看作是一種單例模式侠碧,所謂單例模式,就是
永遠(yuǎn)保持對象的一個(gè)實(shí)例缠黍。
增強(qiáng)的模塊模式舆床,這種模式適合返回自定義對象,也就是構(gòu)造函數(shù)嫁佳。

function Desk() {};
var box = function () {
var age = 100;
function run() {
return '運(yùn)行中...';
}
var desk = new Desk(); //可以實(shí)例化特定的對象
desk.go = function () {
return age + run();
};
return desk;
}();
alert(box.go());

感謝收看本次教程!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谷暮,一起剝皮案震驚了整個(gè)濱河市蒿往,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湿弦,老刑警劉巖瓤漏,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡蔬充,警方通過查閱死者的電腦和手機(jī)蝶俱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饥漫,“玉大人榨呆,你說我怎么就攤上這事∮苟樱” “怎么了积蜻?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長彻消。 經(jīng)常有香客問我竿拆,道長,這世上最難降的妖魔是什么宾尚? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任丙笋,我火速辦了婚禮,結(jié)果婚禮上煌贴,老公的妹妹穿的比我還像新娘御板。我一直安慰自己,他們只是感情好崔步,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布稳吮。 她就那樣靜靜地躺著,像睡著了一般井濒。 火紅的嫁衣襯著肌膚如雪灶似。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天瑞你,我揣著相機(jī)與錄音酪惭,去河邊找鬼。 笑死者甲,一個(gè)胖子當(dāng)著我的面吹牛春感,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虏缸,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鲫懒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刽辙?” 一聲冷哼從身側(cè)響起窥岩,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宰缤,沒想到半個(gè)月后颂翼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晃洒,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年朦乏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了球及。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呻疹,死狀恐怖吃引,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诲宇,我是刑警寧澤际歼,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站姑蓝,受9級(jí)特大地震影響鹅心,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纺荧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一旭愧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宙暇,春花似錦输枯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至型奥,卻和暖如春瞳收,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厢汹。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工螟深, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烫葬。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓界弧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搭综。 傳聞我的和親對象是個(gè)殘疾皇子垢箕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容

  • ??函數(shù)表達(dá)式是 JavaScript 中的一個(gè)既強(qiáng)大有容易令人困惑的特性舰讹。定義函數(shù)的的方式有兩種: 函數(shù)聲明; ...
    霜天曉閱讀 817評論 0 1
  • 作用域和閉包是 JavaScript 最重要的概念之一闪朱,想要進(jìn)一步學(xué)習(xí) JavaScript月匣,就必須理解 Java...
    劼哥stone閱讀 1,177評論 1 13
  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念奋姿。通過函數(shù)可以封裝任意多條語句锄开,而且...
    道無虛閱讀 4,564評論 0 5
  • 第3章 基本概念 3.1 語法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,128評論 0 21
  • 定義函數(shù)的方式有兩種:函數(shù)聲明和函數(shù)表達(dá)式。 函數(shù)聲明的一個(gè)重要特征就是函數(shù)聲明提升称诗,意思是在執(zhí)行代碼前會(huì)先讀取函...
    oWSQo閱讀 665評論 0 0