javascript閉包和立即執行函式
閉包—closure
先看一個閉包的例子。我們想實現一個計數器,最簡單的方法就是定義一個全域性變數,計數的時候將其加1。但是全域性變數有風險,哪裡都有可能不小心改掉它。那區域性變數呢,它只在函式內部有效,函式呼叫完後它就沒了,而且全域性沒法使用。那我們用想讓計數器全域性使用,又不想讓這個變數被隨便修改怎麼辦。這就需要閉包了:
function count(){
var i=0;
return function () {
return ++i;
}
}
這個例子實現了一個簡單的計數器。函式"count()"定義了一個區域性變數“i”,並返回一個內部匿名函式。因為是內部函式,所以它可以訪問其外部函式的區域性變數“i”,並且將其加1並返回。讓我們看下怎麼使用這個計數器。
c1 = count();
console.log(c1()); // print 1
console.log(c1()); // print 2
console.log(c1()); // print 3
c2 = count();
console.log(c2()); // print 1
每次呼叫“count()”函式後就會生成一個計數器,而且不同的計數器之間不干擾。因為兩次呼叫同一個函式,建立的棧是不同的,因此棧內的區域性變數是不同的。上例中,我們生成了全域性計數器“c1”和“c2”,他們都是不帶引數的函式,即“count()”中返回的匿名函式。此後每次呼叫計數器,比如“c1()”,計數器就會自增1並返回。但是由於“count()”函式已經呼叫完畢,我們將無法通過任何其他辦法去修改“count()”中變數“i”的值。這就是閉包最實用的功能,就是將你想操作的變數或物件隱藏起來,只允許特定的方法才能訪問它。
立即執行函式
n年前看到jQuery的原始碼時,很好奇它的最外層結構是這樣的(現在已經不一樣了):
var jQuery = (function() {
...
})();
作為前端小白的我,實在想不通這是為什麼,好好定義一個函式,為啥還要呼叫它。大家知道javascript在es6之前並不嚴格支援面向物件。js的物件其實就是一個map,比如下面的例子:
var car = { speed:0, start:function(){ this.speed=40; }, getspeed:function(){ return this.speed; } }; car.start(); console.log(car.getspeed()); //print 40
這個物件有其成員變數“speed”及成員函式“start”和“getspeed”,但是它的成員變數沒有私有化,同時它也沒有辦法被繼承。要實現物件的繼承,你可以使用建構函式和原型繼承。但怎麼才能將成員變數私有化來實現物件的封裝呢(而且有時候我們也不想那麼麻煩使用原型)?有心的讀者看了上面閉包的介紹,肯定馬上有想法了。對,使用閉包!
function car() {
var speed = 0;
return { //返回的是一個物件
start:function() {
speed = 50;
},
getspeed:function () {
return speed;
}
}
}
var car1 = car();
car1.start();
console.log(car1.getspeed()); //print 50
說了那麼多,跟立即執行函式有什麼關係呢。你再仔細看看上面的例子,你有了閉包函式來幫你建立“car”物件,這個函式就類似於工廠方法,它可以根據你的需要建立多個不同的物件。不過開發的時候經常遇到這樣的情況,就是我們希望物件只有一份,比如jQuery庫的物件,它必須確保整個程式只有一份,多了也沒有。在後端開發模式中,這叫單例模式,可以通過私有化建構函式來實現,那麼在js裡呢?既然函式沒法私有化,那麼唯一的辦法就是讓這個工廠方法能且只能被呼叫一次。不能多次呼叫,那這個函式一定要是匿名函式;而且能被呼叫一次,那就必須在宣告的時候立馬執行。這時候,我們就可以邀請立即執行函數出場了:
var car = (function () {
var speed = 0;
return {
start:function () {
speed=60;
},
getspeed:function () {
return speed;
}
}
})();
car.start();
console.log(car.getspeed()); //print 60
很多人一開始會看錯,認為物件“car”是一個函式,其實它是這個匿名的工廠方法執行完返回的物件,該物件擁有“start”和“getspeed”兩個成員函式,而這兩個函式所需要訪問的“speed”變數對外不可見。同時你無法再次呼叫這個匿名的工廠方法來建立一個相同的物件。是不是很神奇?一個單例的,有著私有成員的物件就這麼建好了。
立即執行函式還有一種寫法就是:
var car = (function() {
...
}());