學(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());