匿名函數:沒有名字的函數;
閉包:可訪問一個函數作用域裡的變量的函數;
一 匿名函數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 普通函數 function box(){ // 函數名是box; return 'Lee'; } box(); // =>Lee; 調用函數; // 匿名函數 function(){ // 匿名函數,會報錯; return 'Lee'; } // 通過表達式自我執行 (function(name){ console.log(name); // =>Lee; })("Lee"); // "()"表示執行函數,並且可以傳參; // 把匿名函數賦值給變量 var box = function(){ // 將匿名函數賦給變量; return 'Lee'; }; console.log(box()); // 調用方式和函數調用相似; // 函數裡的匿名函數 function box(){ return function(name){ // 函數裡的匿名函數,產生閉包; return name; }; }; console.log(box()("Lee")); // 函數box()調用匿名函數,並傳參;二 閉包
閉包:有權訪問另一個函數作用域中的變量的函數;
創建閉包的常見方式:在一個函數內部創建另一個函數;通過另一個函數訪問這個函數的局部變量;
三 this對象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 在閉包中使用this對象可能會導致一些問題;this對象是在運行時基於函數的執行環境綁定的; // 如果this在全局范圍就是指向window,如果在對象內部就指向這個對象; // 而閉包卻在運行時指向window的,因為閉包並不屬於這個對象的屬性或方法; var user = 'Window'; var obj = { user:'Object', getUserFunction:function(){ return function(){ // 閉包不屬於obj,裡面的this指向window; return this.user; }; } }; console.log(obj.getUserFunction()()); // =>Window; // 可以強制指向某個對象 console.log(obj.getUserFunction().call(obj)); // =>Object; // 也可以從上一個作用域中的得到對象 getUserFunction:function(){ var that = this; // 從對象的方法裡得到this;此時that指向obj對象; return function(){ return that.user; } } console.log(obj.getUserFunction()()); // =>Object;四 內存洩漏
1 2 3 4 5 6 7 8 9 10 11 12 // 由於IE的JScript對象和DOM對象使用不同的垃圾收集方式,因此閉包在IE中會導致內存洩漏問題,也就是無法銷毀駐留在內存中的元素; function box(){ var oDiv = document.getElementById('oDiv'); // oDiv用完之後一直駐留在內存中; oDiv.onclick = function(){ alert(oDiv.innerHTML); // 這裡用oDiv導致內存洩漏; }; oDiv = null; // 解除引用; } box(); // 由於匿名函數保存了一個對box()的活動對象的引用,因此就會導致無法減少oDiv的引用數; // 只要匿名函數存在,oDiv的引用數至少也是1;因此它所占用的內存就永遠不會被回收; // PS:如果沒有使用解除引用,那麼要等到浏覽器關閉才得以釋放;五 模仿塊級作用域(定義並立即調用一個匿名函數)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // JS沒有塊級作用域的概念; // 這意味著在塊語句(for語句/if語句)中定義的變量,實際上是在包含函數中而非語句中創建的; function box(count){ for(var i=0; i<count; i++){} // box(2); => count=2; i=2時循環停止,此時i=2; console.log(i); // =>2; i不會因為離開了for塊就失效; } box(2); function box(count){ for(var i=0; i<count; i++){} var i; // 就算重新聲明,也不會覆蓋前面的值; console.log(i); } box(2); // 在JavaScript中,變量i是定義在box()的活動對象中的,因此從它有定義開始,就可以在函數內部隨處訪問它; // 以上兩個例子,說明JavaScript沒有塊級語句的作用域,if(){}/for(){}等沒有作用域; // 如果有作用域的話,出了這個范圍i就應該被銷毀; // JavaScript不會提醒是否多次聲明了同一個變量;遇到這種情況,它只會對後續的聲明視而不見(如果是初始化並賦值,還是會執行的); // 模仿塊級作用域(私有作用域) (function(){ // 這裡是塊級作用域; })(); // 以上代碼定義並立即調用了一個匿名函數;將函數聲明包含在一對圓括號中,表示它實際上是一個函數表達式; // 使用塊級作用域(私有作用域)改寫 function box(count){ (function(){ for(var i=0; i<count; i++){} })(); console.log(i); // 報錯,無法訪問;變量i在私有作用域中,出了私有作用域即被銷毀了. } box(2); // 使用了塊級作用域後,匿名函數中定義的任何變量,都會在執行結束時被銷毀;(i只能在循環中使用,使用後即被銷毀); // 而私有作用域中能夠訪問變量count,是因為這個匿名函數是一個閉包,他能夠訪問包含作用域中的所有變量; // 這種技術經常在全局作用域中被用在函數外部,從而限制向全局作用域中添加過多的變量和函數; // 一般來說,我們都應該盡可能少向全局作用域中添加變量和函數;過多的全局變量和函數很容易導致命名沖突; // 使用塊級作用域,每個開發者既可以使用自己的變量,又不必擔心搞亂全局作用域; (function(){ var box = [1,2,3,4]; console.log(box); // =>[1,2,3,4]; box出來就不被認識了; })(); // 銷毀匿名函數中的變量; console.log(box); // =>box is not defined; // 在全局作用域中使用塊級作用域可以減少閉包占用的內存問題;因為沒有指向匿名函數的引用 // 只要函數執行完畢,就可以立即銷毀其作用域鏈了;六 私有變量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // JavaScript沒用私有屬性的概念;所有的屬性都是公用的; // 不過有一個私有變量的概念:在任何函數中定義的變量,都可以認為是私有變量,因為不能在函數外部訪問這些變量; // 私有變量包括函數的參數/局部變量和在函數內部定義的其他函數; function box(){ var age = 100; // 私有變量,外部無法訪問; } // 而通過內部創建一個閉包,那麼閉包通過自己的作用域鏈也可以訪問這些變量; // 而利用這一點,可以創建用於訪問私有變量的公用方法;特權方法; function Box(){ // 構造函數; var age = 100; // 私有變量; function run(){ // 私有函數; return '運行中...'; }; this.get = function(){ // 對外公共的特權方法; return age+run(); // 將閉包賦值給變量; }; } var box = new Box(); console.log(box.get()); // 可以通過構造方法傳參來訪問私有變量 function Person(name){ var user = name; // 這句其實可以省略; this.getUser = function(){ return user; }; this.setUser = function(name){ user = name; } } var p = new Person('Lee'); console.log(p.getUser()); // =>Lee; console.log(p.setUser('Jack')); console.log(p.getUser()); // =>Jack; // 但是,構造函數模式的缺點是針對每個實例都會創建同樣一組新方法;而使用靜態私有變量來實現特權方法就可以避免這個問題;七 靜態私有變量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // 通過塊級作用域(私有作用域)中定義私有變量或函數,同樣可以創建對外公共的特權方法; (function(){ // 創建私有作用域; var age = 100; // 靜態私有變量; function run(){ return '運行中...'; }; Box = function(){}; // 使用函數表達式定義構造函數; Box.prototype.go = function(){ // 公有(特權)方法;在原型上定義的; return age+run(); }; })(); var box = new Box(); console.log(box.go()); // 100運行中...; // 上面的對象聲明,采用的是Box = function(){}而不是functiong Box(){};並且在聲明Box時沒有使用var關鍵字 // 導致:初始化未經聲明的變量,總是會創建一個全局變量;因此,Box就成了一個全局變量,能夠在私有作用域之外被訪問到; // 因為如果用函數聲明定義構造函數,那麼就變成私有函數了,無法在全局訪問到了,所以要使用函數式定義構造方法; (function(){ var user = ""; Person = function(value){ // 此處定義的Person是全局變量; user = value; // 這裡的構造函數有權訪問私有變量name; }; Person.prototype.getUser = function(){ return user; }; Person.prototype.setUser = function(value){ user = value; } })(); var person = new Person(); person.setUser('Lee'); console.log(person.getUser()); // =>Lee; // 使用了prototype導致方法共享了,而user也就變成靜態屬性了; // 所謂靜態屬性:即共享於不同對象中的屬性;?八 模塊模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 // 簡言之,如果必須創建一個對象並以某些數據對其進行初始化,同時還要公開一些能夠訪問這些私有數據的方法,那麼就可以使用模塊模式; // 之前采用的都是構造函數的方式來創建私有變量和特權方法; // 那麼對象字面量方式就采用模塊模式來創建; var box = { // 字面量對象,也是單例對象:只有一個實例的對象; age:100, // 這是公有屬性,將要改成私有; run:function(){ return '運行中...'; }; }; // 模塊模式私有化變量和函數: var box = function(){ var age = 100; function run(){ return '運行中...'; } return { // 將一個字面量對象作為函數的值返回; go:function(){ // 返回的對象字面量中只包含可以公開的屬性和方法; return age+run(); // 由於這個對象是在匿名函數內部定義的,因此它的公有方法有權訪問私有變量和函數; } }; // 從本質上講,這個對象字面量定義的是單例的公共接口; }(); // 這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時是非常有用的; // 上面直接返回對象的例子,也可以這麼寫: var box = function(){ var age = 100; function run(){ return '運行中...'; } var obj = { // 創建字面量對象; go:function(){ return age+run(); } }; return obj; // 返回剛創建的對象; }(); // 字面量的對象聲明,其實在設計模式中可以看作是一種單例模式; // 所謂單例模式,就是永遠保持對象的只有一個實例; // 增強的模塊模式:適合返回自定義對象,也就是構造函數; function Desk(){}; var box = function(){ var age = 100; function run(){ return '運行中...'; }; var desk = new Desk(); desk.go = function(){ return age+run(); }; return desk; }(); console.log(box.go()); // =>100運行中;九 小結
在JavaScript編程中,函數表達式是一種非常有用的技術;使用函數表達式可以無須對函數命名,從而實現動態編程;
1.函數表達式
函數表達式不同於函數聲明;函數聲明要求有名字,但函數表達式不需要;
沒有名字的函數表達式叫做匿名函數;2.閉包
當在函數內部定義了其他函數時,就創建了閉包.閉包有權訪問包含函數內部的所有變量;原理如下:
在後台執行環境中,閉包的作用域鏈包含著它自己的作用域、包含函數的作用域和全局作用域;
通常,函數的作用域及其所有變量都會在函數執行結束後被銷毀;
但是,當函數返回了一個閉包時,這個函數的作用域將會一直在內存中保存到閉包不存在為止;3.塊級作用域
使用閉包可以在JavaScript中模仿塊級作用域(JavaScript本身沒有塊級作用域的概念);要點如下:
創建並立即調用一個函數,這樣既可以執行其中的代碼,又不會在內存中留下對該函數的引用;
結果就是函數內部的所有變量都會被立即銷毀--除非將某些變量賦值給了包含作用域(即外部作用域)中的變量;4.私有變量
閉包還可以用於在對象中創建私有變量,要點如下:
即使JavaScript中沒有真是的私有對象屬性的概念,但是可以使用閉包來實現公有方法,而通過公有方法可以訪問包含作用域中定義的變量;
可以使用構造函數模式、原型模式來實現自定義類型的特權方法,也可以使用模塊模式來實現單例的特權方法;