一、閉包——closure
先看一個閉包的例子。我們想實現一個計數器,最簡單的方法就是定義一個全局變量,計數的時候將其加1。但是全局變量有風險,哪里都有可能不小心改掉它。那局部變量呢,
它只在函數內部有效,函數調用完后它就沒了,而且全局沒法使用。那我們用想讓計數器全局使用,又不想讓這個變量被隨便修改怎么辦。這就需要閉包了:
function count(){ var i=0; return function () { return ++i; } }
這個例子實現了一個簡單的計數器。函數"count()"定義了一個局部變量“i”,並返回一個內部匿名函數。因為是內部函數,所以它可以訪問其外部函數的局部變量“i”,並且將其加1並返回。讓我們看下怎么使用這個計數器。
c1 = count(); console.log(c1()); //print1 console.log(c1()); //print2 console.log(c1()); //print3 c2 = count(); console.log(c2()); //print1
每次調用“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 () { …… }());
本篇文章的出處:http://www.bjhee.com/js-closure-iif.html