前文說過定義函數的方式有兩種,一種是函數聲明、一種是函數表達式。二者最大的區別是函數聲明提高,即函數的聲明在執行代碼前會先被讀取。編程
遞歸函數是在一個函數中經過名字調用自身的狀況。前面咱們講過的一個計算乘階的函數:數組
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 函數的強大方式。
當在函數內部定義了其餘函數是,就建立了閉包。閉包有權訪問包含函數內部的全部變量。
使用閉包能夠在JavaScript 中模仿塊級做用域(JavaScript中本省沒有塊級做用域的概念)。
閉包還能夠用戶與在對象中建立私有變量。
JavaScript 中的函數表達式和閉包都是及其有用的特性,利用它們能夠實現不少功能。不過由於建立閉包必須維護額外的做用域,因此過分使用它們可能會佔用大量內存。