《Javascript 高階程式設計(第三版)》筆記0xB 函式表示式
目錄
建立一個函式並將它賦值給變數 functionName。這種情況下建立的函式叫做匿名函式(anonymous function),因為 function 關鍵字後面沒有識別符號。匿名函式有時候也叫拉姆達函式,name 屬性是空字串。
//不要這樣做!一般後面的會覆蓋前面的
if(condition){
function sayHi(){
alert("Hi!");
}
} else {
function sayHi(){
alert("Yo!");
}
}
//可以這樣做
var sayHi;
if(condition){
sayHi = function(){
alert("Hi!");
};
} else {
sayHi = function(){
alert("Yo!");
};
}
遞迴
arguments.callee 是一個指向正在執行的函式的指標,因此可以用它來實現對函式的遞迴呼叫。
function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } //嚴格模式下,不能通過指令碼訪問 arguments.callee,訪問這個屬性會導致錯誤。可以使用命名函式表示式來達成相同的結果。 var factorial = (function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } });
閉包
閉包是指有權訪問另一個函式作用域中的變數的函式。
//建立閉包的常見方式,就是在一個函式內部建立另一個函式
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];//訪問了外部函式中的變數 propertyName
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
function compare(value1, value2){
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
無論什麼時候在函式中訪問一個變數時,就會從作用域鏈中搜索具有相應名字的變數。一般來講,當函式執行完畢後,區域性活動物件就會被銷燬,記憶體中僅儲存全域性作用域(全域性執行環境的變數物件)。但是,閉包的情況又有所不同。
//建立函式
var compareNames = createComparisonFunction("name");
//呼叫函式
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除對匿名函式的引用(以便釋放記憶體)
compareNames = null;
閉包與變數
作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函式中任何變數的最後一個值。
/*表面上看,似乎每個函式都應該返自己的索引值,即位置0的函式返回0,位置1的函式返回1,以此類推。但實際上,每個函式都返回 10。
因為每個函式的作用域鏈中都儲存著createFunctions()函式的活動物件,所以它們引用的都是同一個變數i。
當createFunctions()函式返回後,變數 i 的值是 10,此時每個函式都引用著儲存變數 i 的同一個變數物件,
所以在每個函式內部 i 的值都是 10。*/
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
//通過建立另一個匿名函式強制讓閉包的行為符合預期
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物件
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)
每個函式在被呼叫時都會自動取得兩個特殊變數: this 和 arguments。內部函式在搜尋這兩個變數時,只會搜尋到其活動物件為止,因此永遠不可能直接訪問外部函式中的這兩個變數。
不過,把外部作用域中的 this 物件儲存在一個閉包能夠訪問到的變數裡,就可以讓閉包訪問該物件了。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
記憶體洩漏
由於 IE9 之前的版本對 JScript 物件和 COM 物件使用不同的垃圾收集例程因此閉包在 IE 的這些版本中會導致一些特殊的問題。具體來說,如果閉包的作用域鏈中儲存著一個HTML 元素,那麼就意味著該元素將無法被銷燬。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
模仿塊級作用域
JavaScript 沒有塊級作用域的概念。這意味著在塊語句中定義的變數,實際上是在包含函式中而非語句中建立的。
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //計數
}
在 Java、 C++等語言中,變數 i只會在 for 迴圈的語句塊中有定義,迴圈一旦結束,變數 i 就會被銷燬。可是在 JavaScrip 中,變數 i是定義在 ouputNumbers()的活動物件中的,因此從它有定義開始,就可以在函式內部隨處訪問它。
JavaScript 從來不會告訴你是否多次聲明瞭同一個變數;遇到這種情況,它只會對後續的宣告視而不見(不過,它會執行後續宣告中的變數初始化)。匿名函式可以用來模仿塊級作用域並避免這個問題。
/*
* (function(){
* //這裡是塊級作用域
* })();
*以上程式碼定義並立即呼叫了一個匿名函式。將函式宣告包含在一對圓括號中,表示它實際上是一個函式表示式。
*而緊隨其後的另一對圓括號會立即呼叫這個函式。
*/
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //導致一個錯誤!
}
(function(){
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1){
alert("Happy new year!");
}
})();
私有變數
嚴格來講,JavaScript 中沒有私有成員的概念;所有物件屬性都是公有的。不過,倒是有一個私有變數的概念。任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式的外部訪問這些變數。私有變數包括函式的引數、區域性變數和在函式內部定義的其他函式。
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
/*
* 在這個函式內部,有 3 個私有變數: num1、 num2 和 sum。在函式內部可以訪問這幾個變數,但在
*函式外部則不能訪問它們。如果在這個函式內部建立一個閉包,那麼閉包通過自己的作用域鏈也可以訪問這
*些變數。而利用這一點,就可以建立用於訪問私有變數的公有方法。
*/
特權方法(privileged method)
//在建構函式中定義特權方法
function MyObject(){
//私有變數和私有函式
var privateVariable = 10;
function privateFunction(){
return false;
}
//特權方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
靜態私有變數
//在私有作用域中定義私有變數或函式,同樣也可以建立特權方法
(function(){
//私有變數和私有函式
var privateVariable = 10;
function privateFunction(){
return false;
}
//建構函式
MyObject = function(){
};
//公有/特權方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
模組模式
//通過為單例新增私有變數和特權方法能夠使其得到增強
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;
}();