函數表達式的幾種不同的語法形式
var functionName = function(arg0, arg1, arg2){ //函數體 };
這種形式看起來好像是常規的變量賦值語句,即創建一個函數並將它賦值給變量 functionName。這種情況下創建的函數叫做匿名函數(拉姆達函數),因為 function關鍵字后面沒有標識符,
eg1 函數表達式與其他表達式一樣,在使用前必須先賦值。以下代碼會導致錯誤。 sayHi(); //錯誤:函數還不存在 var sayHi = function(){ alert("Hi!"); };
eg2 * : if(true){ function sayHi(){ alert("Hi!"); } } else { function sayHi(){ alert("Yo!"); }
}
表面上看,以上代碼表示在為 true 時,使用一個 sayHi()的定義;否則,就使用另 一個定義,實際上,瀏覽器嘗試修正錯誤的做法並不一致。如果是使用函數表達式,那就沒有什么問題了。
var sayHi; if(true){ sayHi = function(){ alert("Hi!"); }; } else { sayHi = function(){ alert("Yo!"); }; }
遞歸函數
遞歸函數是在一個函數通過名字調用自身的情況下構成的
function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } factorial(4); //24
下面是一個經典的遞歸階乘函數,先把 factorial()函數保存在變量 anotherFactorial 中,然后將 factorial 變量設 置為 null,factorial 已經不再是函數,就會導致錯誤,
function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4));
為了防止調用途中factorial被改變,下面這個這個方式可以解決這個問題
eg: function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } factorial(4); //24
eg2 function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4));
因此,在編寫遞歸函數時,使用 arguments.callee 總比使用函數名更保險。 但在嚴格模式下,不能通過腳本訪問 arguments.callee,訪問這個屬性會導致錯誤
eg 'use strict'; function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4));//出錯
可以使用命名函數表達式來達成相同的結果
eg var factorial = (function f(num){ if (num <= 1){ return 1; }else { return num * f(num-1); } });
這種方式在嚴格模式和 非嚴格模式下都行得通。
閉包
官方解釋:閉包是指有權訪問另一個 函數作用域中的變量的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數
白話解釋:閉包是一種特殊的對象。 它由兩部分組成。執行上下文(代號A),以及在該執行上下文中創建的函數(代號B)。 當B執行時,如果訪問了A中變量對象中的值,那么閉包就會產生。
對於那些有一點 JavaScript 使用經驗但從未真正理解閉包概念的人來說,理解閉包可以看作是某種意義上的重生,突破閉包的瓶頸可以使你功力大增。
eg1: 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; } }; }
這兩行代碼訪問了外部 函數中的變量 propertyName。即使這個內部函數被返回了,而且是在其他地方被調用了,但它仍然可 以訪問變量 propertyName。之所以還能夠訪問這個變量,是因為內部函數的作用域鏈中包含 createComparisonFunction()的作用域 ,而有關如何創建作用域鏈以及作用域鏈有什么作用的細節,對徹底 理解閉包至關重要
function foo() { var a = 20; var b = 30; function bar() { return a + b; } return bar; } var bar = foo(); bar();
上面的例子,首先有執行上下文foo,在foo中定義了函數bar,而通過對外返回bar的方式讓bar得以執行。當bar執行時,訪問了foo內部的變量a,b。因此這個時候閉包產生。
閉包與變量
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000 ); } 輸出的值 6 6 6 6 6
我們直接這樣寫,根據setTimeout定義的操作在函數調用棧清空之后才會執行的特點,for循環里定義了5個setTimeout操作。而當這些操作開始執行時,for循環的i值,已經先一步變成了6。因此輸出結果總為6
利用閉包,修改下面的代碼,讓循環輸出的結果依次為1, 2, 3, 4, 5
for (var i=1; i<=5; i++) { (function(i){ setTimeout( function timer() { console.log(i); }, i*1000 ); })(i) }
借助閉包的特性,每次循環時,將i值保存在一個閉包中,當setTimeout中定義的操作執行時,則訪問對應閉包保存的i值即可。而我們知道在函數中閉包判定的准則,即執行時是否在內部定義的函數中訪問了上層作用域的變量。因此我們需要包裹一層自執行函數為閉包的形成提供條件。 因此,我們只需要2個操作就可以完成題目需求,一是使用自執行函數提供閉包條件,二是傳入i值並保存在閉包中。
閉包中的this對象
匿名函數的執行環境具有全局性,因此其this對象通常指向window。但有時候由於編寫閉包的方式不同,這一點可能不會那么明顯
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)
由於 getNameFunc() 返回一個函數,因此調用 object.getNameFunc()()就會立即調用它返回的函數,結果就是返回一個字符串,為什么匿名函數沒有取得其包含作用域(或外部作用域)的 this 對象呢?
每個函數在被調用時都會自動取得兩個特殊變量:this 和 arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象為止,因此永遠不可能直接訪問外部函數中的這兩個變量
內存泄漏
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); }; }
由於匿名函數保存了一個對 assignHandler()的活動對象的引用,因此 就會導致無法減少 element 的引用數。只要匿名函數存在,element 的引用數至少也是 1,因此它所 占用的內存就永遠不會被回收。不過,這個問題可以通過稍微改寫一下代碼來解決
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
把 element 變量設置為 null。這樣就能夠解除對 DOM 對象的引 用,順利地減少其引用數,確保正常回收其占用的內存。