JS函數表達式


函數表達式是定義函數的一種方式,另一種是之前提到的函數聲明。

//函數聲明
function functionName(args){
    //函數體
}


//函數表達式
var functionName = function(args){
    //函數體
};

 函數聲明和函數表達式之間的區別,主要是函數聲明提升,意思是在執行代碼之前會讀取函數聲明。

沒有名字的函數表達式也叫匿名函數。

 

一、遞歸

遞歸是一個函數通過名字調用自身。

因為函數名可能會發生改變,如果函數內部調用自身的語句仍使用原來的名字,就會發生錯誤,在這種情況下,使用arguments.callee可以解決這個問題。arguments.callee是一個指向正在執行的函數的指針。

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

除了使用arguments.callee,還可以使用命名函數表達式來達成同樣的結果。

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

 

二、閉包

閉包是指有權訪問另一個函數作用域中的變量的函數。

關於作用域:當某個函數被調用時,會創建一個執行環境及對應的作用域鏈。然后,使用arguments和其他命名參數的值來初始化函數的活動對象。作用域鏈就是由一組有序(按調用順序由內到外)的活動對象的引用組成。在函數執行過程中,為讀取和寫入變量的值,就需要在作用域鏈中查找變量。   作用域鏈本質上是一個指向變量對象的指針列表,他只引用但不包含實際對象。  一般來講,當函數執行完畢后,局部活動對象會被銷毀,內存中僅保存全局作用域。但是閉包的情況又有所不同。

創建閉包必須維護額外的作用域:匿名函數作為返回值在函數內部定義,如果在外部通過函數表達式引用並調用這個匿名函數,就形成了閉包。在函數執行后,因為匿名函數仍被引用,所以匿名函數的活動對象和作用域鏈依然存在,這導致匿名函數所在的函數的活動對象仍然處於被引用狀態,留在內存中。  可以通過解除對匿名函數的引用(設置引用值為null)來解決,這樣就釋放了內存。

1. 閉包與變量

閉包保存的是整個變量對象,而不是某個特殊變量。所以對於具有同一變量名的值,閉包只會取得最后一個值。關於這個問題,下面的例子可以清晰地說明:

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

這個函數會返回一個函數數組。表面上看,似乎數組內每個函數都應該返回自己的索引值。但實際上,每個函數都返回10,因為每個函數的作用域鏈中都保存着createFunctions()函數的活動對象,所以他們引用的都是用一個變量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;
}

每循環一次,外層的匿名函數就執行一次,將索引值傳入當前位置的函數里(雖然都叫num,但是每個num都是按值傳遞,都只是一個副本)。

2. 關於this對象

每個函數在被調用時都會自動取得兩個特殊變量:this和arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象為止,因此永遠不可能直接訪問外部函數中的這兩個變量。

this對象是在運行時基於函數的執行環境綁定的。雖然匿名函數在某個函數內定義,但一般匿名函數都是在全局函數中執行,所以其this對象通常指向window。

如果想訪問外部作用域的this對象,可以先在外部函數中將this的值保存在一個閉包能訪問到的變量里。

3. 內存泄漏

如果閉包創建了一個循環引用,就會導致無法減少變量的引用數,因此占用的內存就永遠不會回收。

 

三、 模仿塊級作用域

JS中只有執行環境(函數作用域)的概念,而沒有塊級作用域的概念。所以如果想實現塊級作用域,就要通過匿名函數來模仿。具體步驟是先定義一個函數,然后立即調用它。這樣即可以執行其中的代碼,又不會在內存中留下對該函數的引用。結果就是函數內部的所有變量都會立即被銷毀。

因為函數聲明后面不能跟圓括號,所以我們選擇使用函數表達式的方式。代碼如下:

(function(){
    //這里是塊級作用域
})();

 

四、私有變量

JS中沒有正式的私有對象屬性的概念,但可以使用閉包來實現公有方法,而通過公有方法可以訪問在包含作用域中定義的變量(私有變量)。

任何在函數中定義的變量,都是私有變量,因為不能在函數的外部訪問這些變量。私有變量包括函數的參數、局部變量和在函數內部定義的其他函數。

特權方法是有權訪問私有變量和私有函數的公有方法。有兩種在對象上創建特權方法的方式。第一種是在構造函數中定義特權方法,基本模式如下:

function MyObject(){

    //私有變量和私有函數


    //特權方法
    this.func = function (){
    //有權訪問私有變量和函數
    };
}

但是這種方式針對每個實例都會創建同樣一組新方法,而是用私有靜態變量來實現特權方法可以避免這個問題。

1. 靜態私有變量

在私有作用域中定義私有變量和函數,並省略var構造全局函數及下及相應的特權方法。

(function(){

    var name = "";
    
    Person = function(value){                
        name = value;                
    };
    
    Person.prototype.getName = function(){
        return name;
    };
    
    Person.prototype.setName = function (value){
        name = value;
    };
})();

2. 模塊模式

前面的模式是用於為自定義類型創建私有變量和特權方法的。模塊模式則視為單例創建私有變量和特權方法。單例,指只有一個實例的對象。

在私有作用域中定義私有變量和函數,並返回公有變量和特權方法,由單例接收。

function BaseComponent(){
}

function OtherComponent(){
}

var application = function(){

    //private variables and functions
    var components = new Array();

    //initialization
    components.push(new BaseComponent());

    //public interface
    return {
        getComponentCount : function(){
            return components.length;
        },

        registerComponent : function(component){
            if (typeof component == "object"){
                components.push(component);
            }
        }
    };
}();

3. 增強的模塊模式

和模塊模式類似,不同的是顯式創建要返回的對象,再返回之前可以添加某些屬性和方法對其加以增強。這種模式適合那些單例必須是某種類型的實例的情況。

function BaseComponent(){
}

function OtherComponent(){
}

var application = function(){

    //private variables and functions
    var components = new Array();

    //initialization
    components.push(new BaseComponent());

    //create a local copy of application
    var app = new BaseComponent();

    //public interface
    app.getComponentCount = function(){
        return components.length;
    };

    app.registerComponent = function(component){
        if (typeof component == "object"){
            components.push(component);
        }
    };

    //return it
    return app;
}();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM