度過一個愉快短暫的周末,又可以開始學習了!我愛學習,學習使人進步。今天學習函數表達式,着重學習下閉包函數。
函數表達式
可以在定義的函數聲明之前調用它,但是不能在定義函數表達式之前調用它
/** * 理解函數聲明和函數表達式的區別,學習拉姆達(Lambda)表達式 */
functionName(a);
aFuncton(a);//錯誤 function fuctionName(arg){};//函數聲明 var aFunction=function (arg) {};//函數表達式(匿名函數) /* 函數聲明:在執行代碼之前會先讀取函數聲明,函數聲明可以放在調用它的語句后面。
Lambda表達式:創建匿名函數的一種方法,=>來表示
運算符右邊的表達式對運算符左邊指定的參數執行操作
*/
var x=3,
fun=x=>{returnx*x;};//lambda表達式
遞歸:在函數內部調用自己
/**
* 借用arguments.callee和使用函數命名表達式實現遞歸
*/
//遞歸函數
function factorial(num){
if(num<=1){ return 1; }else{ return num*factorial(num-1); } }; var anotherFactorial=factorial; factorial=null; alert(anotherFactorial(4));//出錯,factorial值發生變化。 //解決方案:1.非嚴格模式下 function factorial(num){ if(num<=1){ return 1; }else{ return num*arguments.callee(num-1); } }; //2.命名函數表達式 var factorial=(function f(num){ if(num<=1){ return 1; }else { return num * f(num - 1); } });
閉包:能夠訪問另一個函數作用域中的變量的函數。
作用域鏈:本質上是一個指向變量對象的指針列表
創建函數時,會創建一個預先包含全局變量對象的的作用域鏈,這個作用域鏈被保存在內部的[[Scope]]屬性中,調用函數時創建一個執行環境,然后復制[[Scope]]屬性中的對象構建起執行環境的作用域鏈,然后有一個函數的活動對象被創建並推入執行環境作用域鏈的前端。
在另一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的作用域鏈中
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:'趙雲'},{name:'馬超'}); //解除對匿名函數的引用,以便釋放內存 compareNames=null;
作用域鏈可以想象成一個列表,里面的函數創建時候內部創建它,這個列表中,最上面的
上面的例子中,有兩個作用域鏈,createComparisonFunction和匿名函數function(object1,object2)的作用域鏈分別是A和B。匿名函數從compareName()被返回后,B包含全局變量對象、createComparisonFunction函數的活動對象和匿名函數的活動對象的引用,createComparisonFunction函數執行完畢A被銷毀,但是它的活動對象被B引用,雖然這樣可以在全局環境中訪問外部這個函數的變量了,但這個外部函數的活動對象已然存在。所以調用函數結束后要解除對匿名函數的引用。
function a(){
var result=[]; for (var i=0;i<10;i++){ result[i]=function () { return i;}; } }
上面的例子中,最后result數組中每個值都是10! 因為i是a函數活動對象中的變量,每個內部函數作用域鏈中都有a的活動對象,所以引用的是同一個i,a函數返回后i=10。
解決方案:使用立即執行函數
1 function a() {
2 var result = []; 3 for (var i = 0; i < 10; i++) { 4 result[i] = function (num) { 5 return function () { 6 return num; 7 } 8 } 9 return result; 10 } 11 }
在作用域中聲明的變量,可能會被其他人不小心用同名的變量給覆蓋掉,作用域鏈的特性,用匿名函數作為一個容器,容器內部可以訪問外部的變量,而外部環境不能訪問容器內部的變量,所以( function(){…} )()內部定義的變量不會和外部的變量發生沖突,相當於命名空間。
閉包中使用this對象
this對象是在運行時基於函數的執行環境綁定的:全局函數,this等於window,而當函數被最為某個對象的方法調用時,this就是那個對象。由於匿名環境具有全局性,它的this通常指向window.
1 var name='Window';
2 var object={ 3 name:'object', 4 getName:function(){ 5 return function () { 6 return this.name 7 } 8 } 9 };; 10 console.log(object.getName()) //Window
例子中getName方返回一個匿名函數,這個匿名函數返回this.name,調用內部匿名函數的時候,你不函數在搜索this和arguments兩個變量時,只會搜索到活動對象為止。
解決方案:
var name='Window';
var object={ name:'object', getName:function(){ var that=this; return function () { return that.name; } } }; console.log(object.getName());
閉包函數會導致內存泄露,在垃圾清理的引用計數模式中,如果有閉包函數作用域鏈中有html元素,則該元素的引用至少一直是1,因此它所占用的內存不會被回收,因此要吧該元素的賦值變量設為null;
模仿塊級作用域:
(function(){})();//把函數聲明轉為函數表達式
立即調用了一個匿名函數
在一個由很多開發人員共同參與的大型應用程序中,過多的全局變量和函數容易導致命名沖突,通過創建私有作用域解決。
私有變量
任何函數中定義的變量,可以認為是私有變量。包括函數的參數,局部變量和函數內部定義跌其它函數。
訪問私有變量方法:1構造函數中創建閉包
靜態私有變量:利用原型。
重點:模塊模式
對象字面量:實際是單例(只有一個實例的對象)。模塊模式就是為單例創建私有變量和特權方法。
1 var singleton=function(){
2 //私有變量和私有函數
3 var privateVaribale=10; 4 function privateFunction(){ 5 return false; 6 } 7 return{ 8 publicProperty:true; 9 publicMethod:funciont(){ 10 privateVaribale++; 11 return privateFunction(); 12 } 13 }; 14 }();
這個模塊模式使用了一個返回對象的匿名函數。在這個匿名函數內部,首先定義了私有變量和函數,有一個對象字面量作為函數的值返回,因此它的公有方法有權訪問私有變量和函數。從本質上來說,這個對象字面量定義的是單例的公共接口。這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時是非常有用的。
模塊模式用來設計一個加載器什么的很有用,專門寫一篇來分析。
