javascript
javascript:闭包的总结
*前言:這次總結閉包,分別參考了《js高級程序設計》、廖雪峰老師的網站、還有《js忍著秘籍》,好了,廢話少說,黑喂狗~~~
---------------------嚴肅分割線-------------------*
1.js函數中的作用域鏈
沒錯,閉包還是要從作用域鏈說起,要理解閉包必須從函數第一次被調用時發生了什么入手,先看一個例子,代碼:
function compare(value1,value2){if(value1 < value2){return -1;}else if(value1 > value2){return 1;}else{return 0;}}var result = compare(5,10); //全局作用域中調用首先定義了一個compare函數,然后又在全局作用域中調用了它。當調用compare()時,會創建一個包含(this,arguments,value1,value2)的活動對象,而全局執行環境的變量對象(this,result,compare)在compare()執行環境的作用域鏈中處于第二位。在這例子中,當調用compare()函數時,會為函數創建一個執行環境,其作用域鏈包含兩個變量對象:本地活動對象和全局對象,在函數中訪問一個變量時,會先從本地作用域中查找,找不到則向上查找外層函數的作用域中是否有,直到全局作用域。當函數執行完畢后,局部活動對象就會被銷毀,內存中僅保存全局作用域,但是閉包情況有所不同。
2.閉包中的作用域鏈
先看一個例子:
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 result = compareNames({name:"Jack"},{name:"Rose"});compareNames = null; //銷毀匿名函數上面的例子中,在createComparisonFunction函數內部定義了一個匿名函數,它的作用域鏈包含外部函數createComparisonFunction()的活動對象和全局變量對象,所以匿名函數中可以訪問createComparisonFunction()函數中的propertyName。
最重要的是:createComparisonFunction()函數在執行完畢后,它的執行環境作用域鏈會被銷毀,其活動對象仍然會留在內存中,這就是為什么上面的代碼中,執行:
var compare = createComparisonFunction("name")之后,createComparisonFunction()函數已經執行完畢,但是仍然可以在下一行代碼中執行比較大小的操作。
創建的比較函數被保存在變量compareNames中,設置它等于null,解除該函數引用,垃圾回收。
3.閉包與變量
副作用:
閉包只能取得外層函數中任何變量的最后一個值。看下面例子:
function createFunctions(){var arr = new Array();for(var i=0; i<10;i++){arr[i] = function(){return i;};}return arr;}var result = createFunctions();var f1 = result[0];var f2 = result[1];var f3 = result[2];//執行函數alert(f1()); //10alert(f2()); //10alert(f3()); //10這個例子中似乎每個函數都應該返回自己的索引值,實際上每個匿名函數返回的都是10。因為每個匿名函數中都保存著createFunctions()函數的活動對象,所以它們引用的是同一個變量i,函數createFunctions()返回后,變量i的值都是10,此時每個函數都引用著保存變量i的同一個變量對象,所以每個函數內部i的值都是10.
注意:上述函數的調用過程:執行createFunctions()函數,并且把函數執行結果賦值給變量result,現在result是一個數組,再把result[0]賦值給f1,f1實際上代表的是內部的匿名函數,現在執行這個函數:f1(),就會得到i的值。
改造
可以通過創建另一個匿名函數強制讓閉包的行為符合預期,看下面例子:
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 final = createFunctions();var f1 = final[0];alert(f1()); //0這次沒有直接把閉包賦值給數組,而是定義了一個匿名函數,并立即執行該函數的結果賦值給數組。這個匿名函數有一個參數num,也就是最終要返回的值,調用這個匿名函數時,傳入了變量i,由于函數參數是按值傳遞的,所以會把變量i的當前值傳遞給num,這個匿名函數內部,又創建并返回了一個訪問num的閉包,這樣,最里面的匿名函數會鎖住外層匿名函數傳遞進來的num值即當前i的值,并且返回num,這樣num就會等于此時的i的值并且賦值給數組。
4.閉包就是匿名函數嗎?
上述代碼中很容易讓人誤解:閉包就是一個匿名函數,其實不然,看下面例子:
var outName = "外面的名字";var later;function outerFunction(){var innerName = "里面的名字";function innerFunction(){alert("I can see the "+outName);alert("I can see the "+innerName);}later = innerFunction;}outerFunction();later();上述代碼中,在outerFunction()函數中,將內部函數innerFunction()賦值給全局變量later,執行完倒數第二步:outerFunction();之后,執行later();依然可以可以訪問到內部變量innerName。
因為在外部函數outerFunction中聲明innerFunction()函數時,不僅聲明了函數,還創建了一個閉包,該閉包不僅包含函數聲明,還包含了函數聲明的那一時刻該作用域中的所有變量。
5.閉包的用處
私有變量
上述例子可以看出,閉包可以用來封裝私有變量,就像java中的在對象內部封裝一個private私有變量一樣。
看下面例子:
上面例子中用構造函數模式創建了一個計數器函數,然后對函數進行實例化,在構造函數內部,我們定義了一個變量num,它的可訪問性只能在構造器內部,定義了一個getNum()方法,該方法只能對內部變量進行讀取,但不能寫入。然后又定義了方法num(),通過最后兩行測試代碼可以看出,可以通過存取方法getNum獲取私有變量,但是不能直接訪問私有變量。
總結:私有變量的意思是,我們如果想對num進行加減乘除的操作,只能在createCounter內部,外部只能訪問內部進行邏輯操作后的值,而不能訪問帶有對num值進行操作的方法,這樣,我們就可以把自己的業務邏輯封裝在函數內部的閉包中,只需要暴露出接口讓外部獲取想要得到的值就可以了,也就是說主動權完全在你定義函數時,外部只能看和獲取,而不能進行對變量值的改變的操作。
上述的目的就是創建一個用于訪問私有變量的公有方法。看下面代碼:
function Person(name){this.getName = function(){return name;};this.setName = function(){name = value;};}//測試var person = new Person("Jack");alert(person.getName()); //Jackperson.setName("Rose");alert(person.getName()); //Rose上面的代碼在構造函數內部定義了兩個方法:setName和getName,這兩個方法都可以在構造函數外部訪問和實用,而且都有權訪問私有變量name,但在Person構造函數外部,沒有任何辦法訪問name,由于這兩個方法是在構造函數內部定義的,所以做為閉包能夠通過作用域鏈訪問name。上述在構造函數中定義特權方法有一個缺點,就是必須要使用構造函數模式來達到這個目的,這樣針對每個實例都會創建同樣一組新方法。
解決辦法:靜態私有變量
看下面代碼:
上述代碼中,Person構造函數與getName()和setName()方法一樣,都有權訪問私有變量name,name變成了一個靜態的、由所有實例共享的屬性。在一個實例上調用setName會影響所有的實例。或者新建一個Person實例都會賦予name屬性一個新值。
上述兩個方法:第二種創建靜態私有變量會因為使用原型而增進代碼復用,但每個實例都沒有自己的私有變量。
模仿塊級作用域
上面舉過例子,js沒有塊級作用域,可以通過匿名函數立即執行把塊級作用域包裹起來,這樣就有了塊級作用域。看下面代碼:
function outputNumbers(count){(function(){for(var i = 0; i<count; i++){alert(i);}})();alert(i); //此處會導致一個錯誤,顯示i是沒有定義的。alert(count);}//測試函數outputNumbers(5);上述函數中,在for循環外部插入了一個私有作用域,在匿名函數中定義的任何變量,都會在匿名函數執行結束時被銷毀。因此變量i只能在for循環中使用,使用后即被銷毀,因此上面的代碼執行會這樣:
1.執行測試函數后,會彈出5個彈窗,會顯示0,1,2,3,4
2.執行完匿名函數后,i即被銷毀,所以執行alert(i);會報錯。
3.可以訪問變量count,因為這個匿名函數時一個閉包,它能夠訪問包含作用域中的所有變量。
這種技術經常在全局作用域中被用在函數外部,從而限制向全局作用域中添加過多的變量和函數,看下面代碼:
(function(){var now = new Date();if(now.getMonth() == 0 && now.getDate() == 1){alert("元旦快樂");}})();上述代碼放在全局作用域中,可以用來確定哪一天時1月1日元旦,now是匿名函數中的局部變量,不用在全局作用域中創建它。
先寫這么多吧,以后再添加~~~~~
總結
以上是生活随笔為你收集整理的javascript:闭包的总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大数据:从入门到XX(一)
- 下一篇: FromBottomToTop团队项目总