《JavaScript高級程序設計》學習筆記(第七章)

函數表達式是JavaScript當中一個既強大又使人困惑的特性,特別是其中涉及到的閉包,更是令許多的初學者困惑不已。前端

在以前的章節中有介紹過,定義函數的方法有兩種:一種是函數聲明。數組

function functionName(arg0, arg1, arg2) {
        //函數體
    }

對於函數聲明,有一個重要的特徵就是能夠把函數聲明放在調用它的語句後面,由於解釋器會在執行語句以前先讀取函數的聲明。閉包

另外一種方法是使用函數表達式。函數

var functionName = function(arg0, arg1, arg2){
        //函數體
    };

函數表達式與函數聲明的一個主要不一樣就是,它必須在定義以後才能使用,不然將會報錯。函數表達式中的函數是一個匿名函數,即function關鍵字後面沒有標識符。既然能夠把函數賦值給變量,也就能夠把函數做爲其它函數的返回值。this

遞歸

JavaScript中的遞歸有個問題,將保存函數的變量賦值給另外一個變量時,由於函數名稱改變了,因此在遞歸調用的時候會出現問題:prototype

function factorial(num){
        if (num <= 1){
            return 1;
        } else {
            return num * factorial(num-1);
        }
    }

    var anotherFactorial = factorial;
    factorial = null;
    alert(anotherFactorial(4)); //出錯!

要解決這個問題,可使用arguments.callee。這個屬性是一個指向正在執行的函數的指針,因此能夠利用它來代替函數名,這就確保遞歸調用時函數名稱改變也不會出錯。可是,在嚴格模式下,使用argments.callee會致使錯誤。咱們可使用命名函數來達成相同的結果:指針

var factorial = (function f(num){
        if (num <= 1){
            return 1;
        } else {
            return num * f(num-1);
        }
    });

閉包

閉包是一個容易使人困惑的概念。閉包是指有權訪問另外一個函數做用域中的變量的函數。建立裝飾的常見方式就是嵌套定義函數。code

咱們在第4章的時候瞭解過做用域鏈。而對於做用域鏈清晰地理解,是理解閉包的重要關鍵。對象

當調用函數的時候,會爲函數建立一個執行環境,並將該環境的活動對象加入到做用域鏈的前端。做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。遞歸

當在一個函數內部定義另外一個函數的時候,外部函數的活動對象會被添加到內部函數的做用域鏈中。通常狀況下,當函數執行完畢後,它的活動對象就會被銷燬。可是,當有內部函數與其構成閉包時,由於內部函數的做用域鏈仍在引用外部函數的活動對象,所以只有當內部的函數也被銷燬後,這個外部活動對象纔會被銷燬。

閉包與變量

閉包只能取得包含函數(即外部函數)中任何變量的最後一個值。

function createFunctions(){
        var result = new Array();
        for (var i=0; i < 10; i++){
            result[i] = function(){
                return i;
            };
        }
        return result;
    }

這個函數返回的函數數組中的每一個函數都會返回10。由於每一個函數的做用域鏈中都保存着createFunctions()的活動對象,因此它們引用的都是同一個變量i。當createFunctions()返回後,i的值是10,因此每一個函數引用保存的變量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;
    }

關於this對象

this對象是在運行時,基於函數的執行環境綁定的。可是,匿名函數的執行環境具備全局性,所以其this對象一般指向window

var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            return function(){
                return this.name;
            };
        }
    };
    alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)

這是由於每一個函數都有本身的this,因此當內部函數在搜索這個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問到外部函數中的this

模仿塊級做用域

以前講過,在JavaScript當中沒有塊級做用域。可是,咱們可使用匿名函數來模仿塊級做用域。

(function(){
        // 這裏是塊級做用域
    })();

在函數的聲明包含在一對圓括號中,表示它其實是一個函數表達式,而緊隨其後的另外一對圓括號會當即調用這個函數。

在須要用到塊級做用域的時候,就能夠這樣使用:

function outputNumbers(count){
        (function () {
            for (var i=0; i < count; i++){
                alert(i);
            }
        })();

        alert(i); //致使一個錯誤!
    }

私有變量

從技術角度來說,JavsScript中是沒有私有成員的概念的,全部對象屬性都是公開的。因可是對於函數而言,函數裏面的變量對外部都是私有的,咱們能夠利用在函數裏面建立一個閉包,來建立用於訪問函數內部私有變量的公有方法。

對於有權訪問私有變量和私有函數的公有方法,咱們稱之爲特權方法(privileged method)。建立特權方法的方式有兩種:一是在構造函數當中定義特權方法。

function MyObject(){
        //私有變量和私有函數
        var privateVariable = 10;
        function privateFunction(){
            return false;
        }

        //特權方法
        this.publicMethod = function (){
            privateVariable++;
            return privateFunction();
        };
    }

對於這個例子而言,變量privateVariable和函數privateFunction()只能經過特權方法publicMethod()來訪問,咱們沒法直接訪問到內部私有的變量。
使用模式的缺點是針對每一個實例都會建立一樣一組新方法,可使用另外一種方法,靜態私有變量來避免這個問題。

靜態私有變量

經過在私有做用域中定義私有變量和函數,也能夠建立特權方法:

(function(){
        //私有變量和私有函數
        var privateVariable = 10;

        function privateFunction(){
            return false;
        }

        //構造函數
        MyObject = function(){
        };
        //公有/特權方法
        MyObject.prototype.publicMethod = function(){
            privateVariable++;
            return privateFunction();
        };
    })();

這個模式使用了函數表達式來定義特權方法,由於函數聲明只能建立局部的函數,一樣地,對於MyObject咱們也沒有使用var關鍵字,這樣可使其成爲一個全局變量。

這個模式與構造函數模式的主要區別在於,私有變量和函數是由實例共享的。因爲特權方法是在原型上定義的,所以全部實例都使用同一個函數。而這個特權方法做爲一個裝飾,問題保存着對包含做用域的引用。

模塊模式

模塊模式是爲單例建立私有變量和特權的方法。所謂單例,即只有一個實例的對象。

通常狀況下,JavaScript是以對象字面量的方式來建立單例對象的。

模塊模式經過爲單例添加私有變量和特權方法可以使其獲得加強,其語法形式以下:

var singleton = function(){
        //私有變量和私有函數
        var privateVariable = 10;

        function privateFunction(){
            return false;
        }

        //特權/公有方法和屬性
        return {
            publicProperty: true,
            publicMethod : function(){
                privateVariable++;
                return privateFunction();
            }
        };
    }();

若是必須建立一個對象並以某些數據對其進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼就可使用模塊模式。

加強的模塊模式

這個模式即在返回對象以前加入對其加強的代碼。這種加強的模塊模式適合那些單例必須是某種類型的實例,同時還必須添加某些屬性或方法對其加以增長的狀況。

var singleton = function(){
    
        //私有變量和私有函數
        var privateVariable = 10;
            function privateFunction(){
            return false;
        }

        //建立對象
        var object = new CustomType();

        //添加特權/公有屬性和方法
        object.publicProperty = true;
        object.publicMethod = function(){
            privateVariable++;
            return privateFunction();
        };

        //返回這個對象
        return object;
    }();

小結

這章主要討論了JavaScript當中的函數表達式與閉包。理解閉包的一個重要基礎就是要透徹理解執行環境和做用域鏈。

函數表達式和閉包都是極其有用的特性,利用它們能夠實現不少功能。不過,由於建立閉包必須維護額外的做用域,因此過分使用它們可能會佔用大量內存。

相關文章
相關標籤/搜索