定義函數表達式的方式有兩種:javascript
// 函數聲明 function functionName(params) { ... }
// 函數表達式有幾種不一樣的方式,下面是最多見的一種 var functionName = function(params) { ... }
sayHi(); // 錯誤,函數還不存在 var sayHi = function () { console.log('Hi!'); };
condition
爲true
時,使用一個定義,不然使用另外一個定義。實際上,在ECMAScript中屬於無效語法,JavaScript引擎會嘗試修正錯誤,將其轉換爲合理狀態。但問題是瀏覽器嘗試修正錯誤的作法並不一致。大多數瀏覽器會返回第二個聲明,忽略condition
的值;Firefox會在condition
爲true的時候返回第一個聲明。所以這種作法很危險,不該該出如今你的代碼中。// 不要這樣作! if (condition) { function sayHi() { console.log('Hi!'); } } else { function sayHi() { console.log('Yo!'); } }
// 能夠這樣作 var sayHi; if (condition) { sayHi = function() { console.log('Hi!'); } } else { sayHi = function() { console.log('Yo!'); } }
createComparisonFunction()
就返回了一個匿名函數。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; } } }
// 經典的遞歸階乘函數 function factorial (num) { if (num <= -1) { return 1; } else { return num * factorial(num - 1); } }
// 把factorial() 函數保存在變量anotherFactorial中 var anotherFactorial = factorial; // 將factorial設置爲null // 如今指向原始函數的引用只剩下anotherFactorial factorial = null; // 原始函數必須執行factorial() // 但factorial再也不是函數,因此致使出錯 anotherFactorial(4); // throw error!
// 非嚴格模式 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); } })
createComparisonFunction()
函數爲例function createComparisonFunction (propertyName) { return function (object1, object2) { // 下面兩行代碼訪問了外部函數中的變量propertyName // 即便這個內部函數被返回了,並且是在其餘地方被調用了 // 它仍然能夠訪問變量 propertyName var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } }
createComparisonFunction()
的做用域。要搞清楚其中細節,必須從理解函數被調用的時候都會發生什麼入手。第4章介紹過 做用域鏈。當某個函數被 調用 時會發生下列事情:前端
function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var result = compare(5, 10);
後臺的每一個執行環境都有一個表示變量的對象——變量對象。全局環境的變量對象始終存在,而像compare()
函數這樣的局部環境的變量對象,則只在函數執行的過程當中存在。java
compare()
函數時,會建立一個預先包含全局變量對象的做用域鏈,這個做用域鏈被保存在內部的[[scope]]屬性中。compare()
函數時,會爲函數建立一個執行環境,而後經過複製函數的[[scope]]屬性中的對象構建起執行環境的做用域鏈。對於本例, compare()
函數的執行環境而言,其做用域鏈中包含兩個變量對象:數組
function createComparisonFunction (propertyName) { return function (object1, object2) { // 下面兩行代碼訪問了外部函數中的變量propertyName // 即便這個內部函數被返回了,並且是在其餘地方被調用了 // 它仍然能夠訪問變量 propertyName // 即爲 createComparisonFunction 的活動對象 var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } } // 建立比較函數 // 調用了 createComparisonFunction() 方法 // 建立了 createComparisonFunction 的活動對象 // 返回內部的匿名函數 保存在 compareNames // createComparisonFunction 執行完畢 // 但它的活動對象仍被 內部匿名函數引用,因此活動對象仍然存在,不會銷燬 var compareNames = createComparisonFunction("name"); // 此時result調用了 保存在 compareNames 的匿名函數 // 該匿名函數保持了對 createComparisonFunction 活動對象的引用 var result = compareNames({ name: "Nicholas" }, { name: "Greg" }); // 即便 compareNames 執行完畢,createComparisonFunction 活動對象依然存在 // 須要手動解除對匿名函數的引用(以便釋放內存) compareNames = null;
compareNames
中,而經過將其設置爲null解除引用,就等於通知垃圾回收例程將其消除。隨着匿名函數的做用域鏈被銷燬,其餘做用域鏈(除了全局做用域)也均可以安全地的銷燬了。圖7-2展現了調用compareNames()
的過程當中產生的做用域鏈之間的關係function createFunctions() { var result = new Array(); for (var i=0; i < 10; i++) { // 賦值給數組元素的是匿名函數自己,不是具體的值 // 因此在 createFunctions() 執行完畢後,調用數組內的函數,返回的是變量i的值 // 而變量i在執行完畢後,等於 10 result[i] = function() { // 返回指向變量 i 的指針 return i; }; } return result; }
result
裏的每一項函數都應該返回本身的索引值。但實際上每個函數返回的都是10
。createFunctions()
函數的活動對象,因此它們引用的都是同一個變量i
。當createFunctions()
函數返回後,變量i
的值是10
,此時每一個函數都引用着保存變量i
的同一個變量對象,因此在每一個函數內部i
的值都是10
.function createFunctions() { var result = new Array(); for (var i=0; i < 10; i++) { // 此時返回的裏層匿名函數調用了外層匿名函數的 num // 裏層匿名函數建立並返回了一個訪問 num 的閉包 // 如此一來 result 數組中的每一個函數都有本身的num變量副本 result[i] = function(num) { // 返回建立的另外一個匿名函數 return function() { return num; }; }(i); } return result; }
this
對象在閉包中使用this
對象也可能會致使一些問題。this
對象是在運行時基於函數的執行環境綁定的:瀏覽器
this
等於window
this
等於那個對象。this
對象一般指向window
(經過call()
apply()
改變函數的執行環境的狀況除外)。但有時候因爲變成寫閉包的方式不一樣,這一點可能不會那麼明顯var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; // 在非嚴格模式下 object.getNameFunc()(); // "The Window"
this
和arguments
。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量(這一點經過圖7-2能夠看的清楚)。this
對象保存在一個閉包可以訪問到的變量裏,就可讓閉包訪問該對象了。var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { var that = this; return function() { return that.name; }; } }; object.getNameFunc()(); // "My Object"
this
的值可能會意外的改變。var name = "The Window"; var object = { name: "My Object", getName: function() { return this.name; } }; // this 指向 object object.getName(); // "My Object" // 加上了括號,看似在引用一個函數 // 但 (object.getName) 和 object.getName 的定義是相同的 // 因此這行代碼與上面的代碼無異 (object.getName)(); // "My Object" // 非嚴格模式 // 賦值語句會返回 object.getName 的匿名函數 // 至關於將匿名函數在全局環境下運行 (object.getName = object.getName)(); // "The Window"
this
的值不能獲得維持,結果就返回了 "The Window" 。HTML
元素,那麼就意味着該元素將沒法被銷燬function assignHandler() { var element = document.getElementById("someElement"); element.onclick = function() { console.log(element.id); }; }
element
元素處理程序的閉包,而這個閉包則又建立了一個循環引用(事件將在第13章討論)。因爲匿名函數保存了一個對assignHandler()
的活動對象的引用,所以就會致使沒法減小element
的引用數。只要匿名函數存在,element
的引用數至少是1。// 如下修改能夠避免這個問題 function assignHandler() { var element = document.getElementById("someElement"); var id = element.id; element.onclick = function() { console.log(id); }; element = null; }
element
。即便閉包不直接引用element
,包含函數的活動對象也仍然會保存一個引用。所以有必要把element
變量設置爲null
function outputNumerbs(cout) { for (var i=0; i < cout; i++) { console.log(i); } console.log(i); // 計數 }
i
只會在for
循環的語句塊中有定義,循環一旦結束,變量i
就會被銷燬。但是在JavaScript中,變量i
是定義在outputNumbers()
的活動對象中的,所以從它有定義開始,就能夠在函數內部隨處訪問它。即便像下面這樣錯誤的從新聲明變量也不會改變值。function outputNumerbs(cout) { for (var i=0; i < cout; i++) { console.log(i); } var i; // 從新聲明變量 console.log(i); // 計數 }
(function() { // 這裏是塊級做用域 ... })()
// 常見的代碼片斷 // 定義了一個函數,而後當即調用它 var someFunction = function() { // 這裏是塊級做用域 ... }; someFunction();
JavaScript
將function
關鍵字當作一個函數聲明的開始,而函數聲明後面不能跟圓括號。(函數表達式能夠)function() { // 這裏是塊級做用域 ... }() // 出錯!
(function() { // 這裏是塊級做用域 ... }())
function outputNumbers(cout) { // 這裏是一個閉包,匿名函數能夠訪問 cout (function () { for (var i=0; i < cout; i++) { console.log(i); } })(); // 在這裏調用變量 i 會報錯 console.log(i); // throw error }
(function() { var now = new Date(); if (now.getMonth() == 0 && now.gettDate() == 1) { console.log('Haapy new year!"); } })();
有權訪問私有變量和私有函數的公有方法稱爲 特權方法(privileged method) 。有兩種建立特權方法的方式:安全
// 構造函數Person // 入參 name 是它的私有變量 function Person(name) { this.getName = function() { return name; }; this.setName = function(value) { name = value; }; } var person = new Person("Nicholas"); console.log(person.getName()); // "Nicholas" person.setName("Greg"); console.log(person.getName()); // "Greg"
(function() { // 私有變量 var privateVariable = 10; // 私有函數 function privateFunction() { return false; } // 構造函數 // 這裏沒有使用var操做符,自動建立全局變量 // 嚴格模式下不能使用 MyObject = function() {}; // 公有/特權方法 MyObject.prototype.publicMethod = function() { privateVariable++; return privateFunction(); }; })();
(function() { var name = ""; Person = function(value) { name = value; }; Person.prototype.getName = function() { return name; }; Person.prototype.setName = function (value) { name = value; }; })(); var person1 = new Person("Nicholas"); console.log(person1.getName()); // "Nicholas" person1.setName("Greg"); console.log(person1.getName()); // "Greg" var person2 = new Person("Michael"); console.log(person1.getName()); // "Michael" console.log(person2.getName()); // "Michael"
Person
構造函數與getName()
setName()
方法同樣,都有權訪問私有變量name
。name
變成了一個靜態的、由全部實例共享的屬性。var application = function() { // 私有變量和函數 var components = new Array(); // 初始化 components.push(new BaseComponent()); // 公共 return { getComponentCuont: function() { return components.length; }, registerComponent: function(component) { if (typeof component == "object") { components.push(component) } } }; }();
application
對象。Object
的實例,由於最終要經過一個對象字面量來表示他。application
對象必須是BaseComponent
的實例,能夠以下代碼var application = function() { // 私有變量和函數 var components = new Array(); // 初始化 components.push(new BaseComponent()); // 建立 application 的一個局部副本 var app = new BaseComponent(); // 公共接口 app.getComponentCuont = function() { return components.length; }; app.registerComponent = function(component) { if (typeof component == "object") { components.push(component); } }; // 返回這個副本 return app; }();