Javascript – Closure

http://www.w3schools.com/js/js_function_closures.asp

http://javascriptissexy.com/understand-javascript-closures-with-ease/

好困難的Closure(匿名函數),雖然PHP也有(在這),但是還真不是很懂。

Closure:當一個函數(outer)裡面再包含函數(inner),裡面的函數稱之,第二篇文章有介紹它的特點。javascript變數有個特點,函數裡的變數可以直接存取上一層的變數。如果以一層函數來說,它的上一層就是global變數,如果是兩層,第一層是outer函數,第二層是inner函數(closure),第二層能夠直接存取outer的變數,當然global的也能存取。

第一:inner函數(closure)能夠存取outer函數的變數跟參數,即便outer已經被返回。看底下例子比較清楚,mjName變數呼叫celebrityName()函數,結果返回lastName()函數,該函數的內容除了返回自己的參數(theLastName),還返回屬於celebrityName的變數(nameIntro)跟參數(firstName),但是第11行才執行mjName,照理說nameIntro跟theLastName應該沒有值才對,但是它仍然能夠抓到celebrityName的變數值跟參數值。

function celebrityName(firstName) {
    var nameIntro = "Your name is ";
    function lastName(theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }

    return lastName;  //返回lastName函數
}

var mjName = celebrityName("Michael");  //mjName等於lastName函數
mjName("Jackson");  //呼叫lastName函數
//結果, Your name is Michael Jackson

第二:closure存取outer函數的變數是參考。依據第一點,closure可以抓到outer的變數,而且當它改變,closure抓的會跟著改變。mjID.getID()第一次執行,抓到的celebrityID是celebrityID()函數初始化的999,當mjID.setID(567)會去設定outer函數的變數celebrityID值,改為567,所以第二次的mjID.getID()結果為567。

function celebrityID() {
    var celebrityID = 999;
    
    //返回兩個closure
    return {
        getID: function() { 
            return celebrityID; 
        },
        setID: function(theNewID) {
            celebrityID = theNewID;
        }
    };
}

var mjID = celebrityID();  //等於getID()跟setID()兩個函數
mjID.getID();  // 999
mjID.setID(567);
mjID.getID();  // 567

第三:closure出錯。挖,好簡易的說法。當closure被包在Loop中,可能會出錯,直接看例子。因為outer的i變數在loop時會一直更新,最後一次loop變成3,所以匿名函數抓到的是最後更新後的值。

// 錯誤例子
function celebrityIDCreator(theCelebrities) {
    var i;
    var uniqueID = 100;

    for (i = 0; theCelebrities.length; i++) {
        theCelebrities[i]["id"] = function() {
            return uniqueID + i;
        }
    }
    
    return theCelebrities;  //返回celebrityIDCreator的參數
}

var actionCelebs = [
    { name: "Stallone", id: 0 },
    { name: "Cruise", id: 0 },
    { name: "Willis", id: 0}
];

var createIdForActionCelebs = celebrityIDCreator(actionCelebs);
//上一行返回的結果是:
// theCelebrities[0]["id"] = function() { return uniqueID + i ;}
// theCelebrities[1]["id"] = function() { return uniqueID + i ;}
// theCelebrities[2]["id"] = function() { return uniqueID + i ;}
// 執行完Loop,i已經變成3。

var stalloneID = createIdForActionCelebs[0];

//id元素是函數,呼叫時需用id()。
console.log(stalloneID.id());  //output 103 = 100+3

var cruiseID = createIdForActionCelebs[1];
console.log(cruiseID.id());  //output 103 = 100+3

上面的錯誤可以使用立即函數來修正,立即函數可以參考這一篇

function celebrityIDCreator(theCelebrities) {
    var i;
    var uniqueID = 100;    
    for(i = 0; i < theCelebrities.length; i++) {
        theCelebrities[i]["id"] = function(j) {
                return function() {
                    return uniqueID + j;
            }();  //立即函數,會立馬執行,所以第1個loop回傳(100+0)、第2個loop回傳(100+1)、第2個loop回傳(100+2)。
        }(i);  //函數{}後方多了個()表示立即函數,會立馬執行,i會傳入j。
    }
    
    return theCelebrities;
}
var createIdForActionCelebs = celebrityIDCreator(actionCelebs);
var stalloneID = createIdForActionCelebs[0];
//id在這個寫法已經不是函數,接收回傳的值,所以是屬性。
console.log(stalloneID.id);  //output 100
Advertisements

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s