1. 程式人生 > >javascript閉包和立即執行函式

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() {
    ...
}());