js知識梳理6:關於函數的要點梳理(2)(做用域鏈和閉包)

寫在前面

注:這個系列是本人對js知識的一些梳理,其中很多內容來自書籍:Javascript高級程序設計第三版和JavaScript權威指南第六版,感謝它們的做者和譯者。有發現什麼問題的,歡迎留言指出。前端

1.執行環境

  • 執行環境簡稱「環境」,定義了變量或函數有權訪問的其餘數據。每一個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。
  • 全局執行環境是最外圍的一個執行環境。在Web瀏覽器中,全局執行環境被認爲是window對象,所以全部全局變量和函數都是做爲window對象的屬性和方法建立的。
  • 某個執行環境中的全部代碼執行完畢,環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬。
  • 執行流:每一個函數都有本身的執行環境,當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。
  • 代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(scope chain),用途是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象做爲變量對象。活動對象在最開始時只包含arguments 對象。做用域鏈的下一個變量對象來自包含環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象:
var color = "blue";
function changeColor() {
    if(color == "blue"){
        color = "red";
    }else{
        color = "blue";
    }
}
changeColor();
console.log("color is:" + color);//red

例子中,函數changeColor()的做用域鏈包含兩個對象:它本身的變量對象(其中定義着arguments對象)和全局環境的變量對象。能夠在函數內部訪問變量 color,就是由於能夠在做用域鏈中找到它數組

var color = "blue";
function changeColor() {
    var anotherColor = "red";

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;

        //這裏能夠訪問color、anotherColor和tempColor
    }

    //這裏能夠訪問color和anotherColor,但不能訪問tempColor
    swapColors();
}
// 這裏只能訪問color 
changeColor();

以上共涉及3個執行環境:全局環境、changeColor()的局部環境和swapColors()的局部環境。顯然,內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量和函數瀏覽器

2.閉包

閉包是指有權訪問另外一個函數做用域中的變量的函數。建立閉包的常見方式,就是在一個函數內部建立另外一個函數。閉包

function createComparisonFunction(propertyName) {
    return function (object1, object2) {
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if(value1<value2){
            return -1;
        }else if(value1>value2){
            return 1;
        }else{
            return 0;
        }
    }
}
//建立函數
var compareNames = createComparisonFunction("name");
//調用函數
var result = compareNames({name:'jaychou'},{name:'xiaoming'});
//解除對匿名函數的引用(釋放內存)
compareNames = null;

以上過程:函數

1.定義函數內部的函數時,會將它的包含函數的活動對象添加到它的做用域鏈中。在此例中,在匿名函數被返回後,它的做用域鏈初始化爲包含createComparisonFunction()函數的活動對象和全局變量對象。設計

2.createComparisonFunction()函數在執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象,結果就是隻是createComparisonFunction()的執行環境的做用域鏈會被銷燬,其活動對象會留在內存中。code

3.直到代碼中將匿名函數置爲null釋放內存後,createComparisonFunction()的活動對象纔會被銷燬。對象

注意:因爲閉包會攜帶包含它的函數的做用域,因此會比其餘函數佔用更多的內存,過多使用閉包會致使內存佔用過多,因此在頗有必要時才考慮使用閉包。ip

3.閉包的最多見問題

function createFunctions() {
    var result = new Array();
    for(var i=0;i<10;i++){
        result[i] = function () {
            return i;
        };
    }
    return result;
}
console.log(createFunctions()[4]());//會打印10

數組裏面的每一個函數都是打印10,由於每一個函數的做用域鏈中都保存着createFunctions()函數的活動對象,因此他們引用的都是同一個變量i。當函數數組被返回時,變量i的值是10,因此就是上面的結果了。經過建立另外一個匿名函數的改造以下符合預期:內存

function createFunctions() {
    var result = new Array();
    for(var i=0;i<10;i++){
        result[i] = function (num) {
            return function () {
                return num;
            }
        }(i);
    }
    return result;
}
console.log(createFunctions()[4]());//會打印4

沒有直接把閉包賦值給數組,而是定義了一個匿名函數,並將當即執行該匿名函數的結果賦給函數,函數參數是按值傳遞的,因此會把變量i的當前值複製給參數num。

在這個當即執行函數裏面建立並返回了一個訪問num的閉包。這個閉包被返回時都準確包含了對應的包含環境的活動對象的num值。

4.閉包的常見做用

  • 給構造函數建立私用變量和私有函數,並定義特權方法;
  • 建立單例,在函數最後返回公用方法

等等

整體而言,閉包對於咱們理解執行環境,理解做用域鏈頗有幫助,但日常若是不是頗有必要就不要用,佔用內存比較多

相關文章
相關標籤/搜索