JavaScript學習 6、函數表達式

 前文說過定義函數的方式有兩種,一種是函數聲明、一種是函數表達式。二者最大的區別是函數聲明提高,即函數的聲明在執行代碼前會先被讀取。編程

遞歸

 遞歸函數是在一個函數中經過名字調用自身的狀況。前面咱們講過的一個計算乘階的函數:數組

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

console.log(factorial(5));    //120

咱們知道,函數名只是一個引用,因此也能夠進行賦值,當factorial 被賦值爲null或者其餘函數引用時,就會發生錯誤,以下:閉包

var anotherFactorial = factorial;
factorial = null;
anotherFactorial(5);   //TypeError: factorial is not a function

前文講過,使用arguments.callee 能夠解決問題。函數

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

console.log(factorial(5));    //120
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5));   //120

可是在嚴格模式下,使用arguments.callee 會致使錯誤。不過可使用命名函數表達式來達到相同的效果。this

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

console.log(factorial(5));    //120
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5));   //120

這種方式在嚴格模式和非嚴格模式下都能行得通。spa

閉包

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

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 obj1 = {
    name: "Lilei",
    age: 18
};
var obj2 = {
    name: "HanMeimei",
    age: 17
};
console.log(compareNames(obj1, obj2));
compareNames = null;

這裏須要理解compareNames 函數的做用域鏈,函數的做用域鏈保存在內部的[[Scope]] 屬性中,當函數被調用的時候就爲函數建立一個執行環境,而後經過複製函數的 [[Scope]] 屬性中的對象構建其執行環境的做用域鏈。做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。指針

createComparisonFunction 函數做用域鏈包括自身的變量 和 全局變量,compareNames 函數的做用域鏈包括 自身變量 和 createComparisionFunction 的變量 和 全局變量。code

當createComparisonFunction 退出時,返回一個compareNames 函數,自身的做用域鏈銷燬, 可是自身的活動對像因爲被compareNames 引用,因此仍然會留在內存中,知道compareNames 函數被銷燬( compareNames = null; )對象

1.閉包與變量

做用域鏈的這種配置機制引出了一個值得注意的反作用,即閉包只能取得包含函數中任何變量的最後一個值。

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

var res = createFunctions();
res.forEach(function(value, index, array){
    console.log(value.call());   // 10 10 10 ... 10 
});

函數返回十個函數組成的數組,數組中的十個函數的做用域鏈都是自身和createFunctions 的對象,讀取變量 i 值時,找到做用域鏈的 createFunctions 對象,在數組中的函數調用的時候,createFunctions 對象中的 i 已經變成了10, 因此返回的函數返回值都是 10。

解決辦法是再建立一個匿名函數,在做用域鏈中隔離開自身和 createFunctions 的對象。

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

var res = createFunctions();
res.forEach(function(value, index, array){
    console.log(value.call());   // 0 1 2 ... 9
});

本例中,咱們沒有直接把閉包賦值給數組,而是定義了一個匿名函數,並將當即執行該匿名函數的結果賦給了屬豬。這裏的匿名函數有一個參數 num, 也就是最終的函數要返回的值。在調用每一個匿名函數時,咱們傳入了變量 i 。 因爲函數參數是按值傳遞的,因此就會將變量i的當前值複製給 num。而在這個匿名函數內部,有建立並返回了一個訪問num 的閉包。這樣一來,result 數組中每個函數都有本身 num 變量的一個副本,所以就能夠返回各自不一樣的數值了。

2.閉包的this對象

在閉包中使用 this 翠系那個也可能會致使一些問題。匿名函數的執行環境具備全局性,所以其this對象一般指向 global。

var global = function(){
    return this;
}();
global.name = "The global";

var object = {
    name: "My Object",
    getNameFunc: function(){
        return function(){
            return this.name;
        };
    }
}
console.log(object.getNameFunc()());  //The global

前面說過,每一個函數在被調用時都會自動取得兩個特殊變量:this 和 arguments。 內部函數在搜索這兩個變量時,只會搜索到其活動對象位置,所以永遠不可能直接訪問外部函數中的者兩個變量。

模仿塊級做用域

 如前所述,JavaScript 中沒有塊級做用域的概念,這意味着在塊語句中定義的變量,其實是在包含函數中而非語句中建立的。即便後面從新聲明變量,也於事無補。

for(var i = 0; i<10; i++){

}
var i;
console.log(i);  //10

 

匿名函數能夠解決這個問題,用做塊級做用域(一般被稱爲私有做用域)的匿名函數的語法以下:

(function(){
    //todo code 塊級做用域
})();

 

以上代碼定義並當即調用了一個匿名函數。將函數聲明包含在一對全括號中,表示它其實是一個函數表達式。而金穗氣候的另外一對圓括號會當即調用這個函數。

(function(){
    for(var i =0; i<10; i++){
    }
})();
console.log(i);  //ReferenceError: i is not defined

 

這種作法能夠減小閉包占用內存的問題,由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈了。

私有變量

 任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數的外部訪問這些變量。私有變量包括函數的參數、局部變量 和 在函數內部定義的其餘函數。

咱們把有權訪問私有變量和私有函數的共有方法稱爲特權方法(privileged method)。

有兩種在對象上建立特權方法的方式:

第一種:在構造函數中定義特權方法。

function MyObject(){
       //私有變量
    var privateVar = 10;
        //私有方法
    function privateFunc(){
        return false;
    }
        //特權方法
    this.publicMethod = function(){
        privateVar++;
        return privateFunc();
    }
}

 

 定義特卻方法有一個缺點,就是你必須使用構造函數模式來達到這個目的。構造函數構建對象的缺點是對每一個實例都會建立一樣一組方法。

第二種:靜態私有變量

(function(){
    //私有變量 私有方法
    var privateVar = 10;
    function privateFunc(){
        return false;
    }
        //構造函數
    MyObject = function(){};
        //公有/特權方法
    MyObject.prototype.publicMethod = function(){
        privateVar++;
        return privateFunc();
    }
})();

 

這個模式建立了一個私有做用域,兵在其中封裝了一個構造函數及相應的方法。在私有做用域中首先定義了私有變量和私有函數,而後又定義了構造函數和公有方法。公有方法是在原型上定義的,這一點體現了典型的原型模式。須要注意的是,這個模式在定義構造函數時沒有使用函數聲明,而是使用了函數表達式。函數聲明只能建立局部函數,但那不是咱們想要的。出於一樣的緣由,咱們也沒有在聲明MyObject 時使用var 關鍵字。記住:初始化未經聲明的變量,老是會建立一個全局變量。所以MyObject就i成了一個全局變量,可以在私有做用域以外被訪問到。可是在嚴格模式下,給未經聲明的變量賦值會致使錯誤。

多查找做用域鏈中的一個層次,就會在必定程度上影響查找的速度。這正是使用閉包和私有變量的一個明顯的不足之處。

 

模塊模式

 前面的模式用於爲自定義類型建立私有的變量和特權方法。而道格拉斯所說的模塊模式則是爲單利建立私有變量和特權方法。所謂單例(singleton),指的就是隻有一個實例的對象。按照慣例,JavaScript是以對象字面量的方式來建立單例對象的。

var singleton = {
    name: value,
    method: function(){
    
    }
};

 

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

var singleton = function(){
    var privateVar = 10;
    function privateFunc(){
        return false;
    }
    return {
        publicProperty:  true,
        publicMethod: function(){
            privateVar++;
            return privateFunc();
        }
    };
}();

 

這個模塊模式使用了一個返回對象的匿名函數。在這個匿名函數的內部,首先定義了私有變量和函數。而後,將一個對象字面量做爲函數的值返回。返回的對象字面量中質保含能夠公開的屬性和方法。

 加強的模塊模式

改進模塊模式,就是在返回對象以前加入對其加強的代碼。這種加強的模塊模式適合那些單例必須是某種類型的實例,同事還必須添加某些屬性和(或)方法對其甲乙加強的狀況。

var singleton = function(){
    var privateVar = 10;
    function privateFunc(){
        return false;
    }
    //建立對象
    var object = new CustomType();
    object.publicProperty = true;

    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    return object;
    
}();

 

小結

 在JavaScript 編程中,函數表達式是一種很是有用的技術。使用函數表達式能夠無需對函數命名,從而實現動態編程。匿名函數,也稱爲拉姆達函數,是一種使用JavaScript 函數的強大方式。

  • 函數表達式不一樣於函數聲明。函數聲明要求有名字,但函數表達式不須要。沒有名字的函數表達式也叫匿名函數。
  • 在沒法肯定如何引用函數的狀況下,遞歸函數就會變得比較複雜。
  • 地櫃函數應該始終使用 arguments.callee 來遞歸調用自身,不要使用函數名----函數名可能會發生變化。

當在函數內部定義了其餘函數是,就建立了閉包。閉包有權訪問包含函數內部的全部變量。

  • 在後臺執行環境中,閉包的做用域鏈包含着它本身的做用域、包含函數的做用域 和全局做用域。
  • 一般,函數的做用域及其全部變量都會在函數執行結束後銷燬。
  • 可是,當函數返回了一個閉包是,這個函數的做用域將會一直在內存中保存到閉包不存在爲止。

使用閉包能夠在JavaScript 中模仿塊級做用域(JavaScript中本省沒有塊級做用域的概念)。

  • 建立並當即調用一個函數,這樣既能夠執行其中的代碼,有不會在內存中留下對該函數的引用。
  • 結果就是函數內部的全部變量都會被當即銷燬----除非將某些變量賦值給了包含做用域(即外部做用域)中的變量。

閉包還能夠用戶與在對象中建立私有變量。

  • 即便JavaScript 中沒有正式的私有變量屬性的概念,但可使用閉包來實現公有方法,而經過公有方法能夠訪問包含做用域中定義的變量。
  • 有權訪問私有變量的公有方法叫特權方法。
  • 可使用構造函數模式、原型模式來實現自定義類型的特卻方法,也可使用模塊模式、加強的模塊模式來實現單例的特權方法。

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

相關文章
相關標籤/搜索