函數運算式
1、JavaScript中定義函數有2鐘方法:
1-1.函式宣告:
複製代碼 代碼如下:
function funcName(arg1,arg2,arg3){
//函數體
}
①name屬性:可讀取函數名。非標準,瀏覽器支援:FF、Chrome、safari、Opera。
②函式宣告提升:指執行代碼之前會先讀取函式宣告。即函數調用可置於函式宣告之前。
1-2.函數運算式:
複製代碼 代碼如下:
var funcName = function(arg1,arg2,arg3){
//函數體
};
①匿名函數(anonymous function,或拉姆達函數):function關鍵字後無標識符,name屬性值為空白字串。在把函數當成值使用時,都可用匿名函數。
②類似其他運算式,函數運算式使用前需先賦值,故不存在"函式宣告提升"那樣的作用。
③ECMAScript中的無效函數文法:
複製代碼 代碼如下:
if判斷中的重複函式宣告
if(condition){
function sayHi(){
alert("Hi!");
}
} else {
function sayHi(){
alert("Yo!");
}
}
瀏覽器JavaScript引擎修正錯誤差異:大多瀏覽器會返回第二個聲明,忽略condition;FF則會在condition為true時返回第一個聲明。
使用函數運算式可解決並實現:
複製代碼 代碼如下:
if判斷 函數運算式
var sayHi;
if(condition){
sayHi = function(){
alert("Hi!");
}
} else {
sayHi = function(){
alert("Yo!");
}
}
2、遞迴
遞迴函式,是在一個函數中通過名字調用自身的情況下構成的。
複製代碼 代碼如下:
function factorial(num){ //一個經典的遞迴階乘函數
if (num <= 1){
return 1;
} else {
return num * factorial(num-1);
}
}
①若使用下列代碼調用該函數,會出錯:
複製代碼 代碼如下:
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));
將factorial()函數儲存到變數anotherFactorial中後,將factorial變數設為null後不再引用函數,而anotherFactorial(4)中要執行factorial()函數,故出錯。
使用argument.callee(指向正在執行的函數的指標)可解決:
複製代碼 代碼如下:
解決方案
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //24
在非strict 模式,使用遞迴函式時,用argument.callee代替函數名更保險
在strict 模式下,使用argument.callee會出錯,可用函數運算式 代替 函式宣告:
複製代碼 代碼如下:
函數運算式代替函式宣告
var factorial = function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
}
4、閉包
指有權訪問另一個函數範圍中的變數的函數。(常見形式為函數嵌套)
複製代碼 代碼如下:
function wai(pro){
return function(obj1,obj2){
var val1 = obj1[pro];
var val2 = obj2[pro];
if(val1<val2){
return -1;
}else if(val1>val2){
return 1;
}else{
return 0;
};
}
}
return匿名函數時,匿名函數的範圍鏈初始化為包含函數的使用中的物件和全域變數對象。即匿名函數包含wai()函數的範圍。
每個函數被調用時,會建立一個執行環境、一個變數對象 及 相應的範圍鏈。
4-1.執行環境 及 範圍
執行環境execution context簡稱環境,定義了變數和函數有權訪問的其他資料,並決定他們的各自行為。
①每個執行環境都有一個變數對象variable object,儲存環境定義的所有變數和函數。該對象無法編碼訪問,但解析器在處理資料時會在後台使用它。
全域變數對象是最外圍的一個執行環境。在Web瀏覽器中被認為是window對象,故所有全域對象和函數都是window對象的屬性和方法建立的。
執行環境中的代碼執行完後,該環境就被銷毀,儲存其中的變數和函數定義也隨之銷毀。
②代碼在環境中執行時,會建立變數對象的一個範圍鏈scope chain,用於保證對執行環境有權訪問的所有變數和函數的有序訪問。
範圍鏈前端,始終是當前執行的代碼所在環境的變數對象。當該環境為函數時,會將使用中的物件作為變數對象。
使用中的物件最開始只包含一個變數,即argumnt對象。
範圍鏈中的下一個變數對象來自包含環境,而下一個變數對象來自下一個包含環境,直至延續到全域執行環境。
③標識符解析:從前段開始,沿著範圍鏈一級一級地搜尋標識符的過程。【找不到通常會導致錯誤發生】
4-2.函數建立、執行時:
複製代碼 代碼如下:
function compare(val1,val2){
if(val1<val2){
return -1;
}else if(val1>val2){
return 1;
}else{
return 0;
};
}
var result = compare(5 , 10);
①建立函數compare()時,會建立一個預先包含全域變數對象的範圍鏈,並儲存在內部[[scope]]屬性中。
②局部函數compare()的變數對象,只在函數執行的過程中存在。
當調函數時,會建立一個執行環境,再通過複製函數的[[scope]]屬性中的對象 構建起執行環境的範圍鏈。
③第一次調用函數時,如compare(),會建立一個包含this、argument、val1 和 val2的使用中的物件。
④全域執行環境的變數對象(包括this、result、compare)在compare()執行環境的範圍鏈中處於第二位。
⑤範圍鏈 本質是一個指向變數對象的指標列表,只引用但不實際包含變數對象。
⑥無論什麼時候在函數中訪問一個變數,都會行範圍鏈中搜尋具有相應名字的變數。
4-3.閉包的範圍鏈
在另外一個函數內部定義的函數會將包含函數的使用中的物件添加到它的範圍鏈中。
①將函數對象賦值null,等於通知記憶體回收常式將其清除,隨著函數範圍鏈被銷毀,其範圍鏈(不除了全域範圍)也會被安全銷毀。
②由於閉包會攜帶包含函數的範圍,所以會比其他函數佔用更多記憶體。
4-4.閉包與變數
範圍鏈的一個副作用:閉包只能取得包含函數中任何變數的最後一個值。
複製代碼 代碼如下:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
①createFunctions()函數,將10個閉包賦值給數組result,再返回result數組。每個閉包都返回自己的索引,但實際上都返回10。
因為每個函數(閉包)的範圍鏈中都儲存著createFunctions()函數的使用中的物件,所以它們引用的是同一個變數i,當createFunctions函數執行完後i的值10,故閉包中的i也都為10。
②解決辦法,不使用閉包,建立一個匿名函數,將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;
}
建立一個每次迴圈都會執行一次的匿名函數:將每次迴圈時包圍函數的i值作為參數,存入匿名函數中。因為函數參數是按值傳遞的,而非引用,所以每個匿名函數中的num值 都為每此迴圈時i值的一個副本。
4-5.this對象
this對象是在運行時基於函數的執行環境綁定的。
在全域函數中,this等於window;當函數被某對象調用時,this為該對象。
匿名函數的執行環境有全域性,其this對象通常指window。通過call()或spply()改變函數執行環境時,this指向其對象。
①每個函數在被調用時,都會自動取得兩個特殊變數:this和argument。內建函式在搜尋這兩個變數時,只會搜尋到期使用中的物件為止,永遠不可能訪問外部函數的這兩個變數。
不過將外部範圍的this對象儲存在一個閉包能訪問的變數裡,就可讓閉包訪問該對象。
複製代碼 代碼如下:
閉包 訪問外部函數的this對象
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"MyObject"
包圍函數的argument對象 也可通過此方法被閉包訪問。
5、函式宣告 轉換為 函數運算式
JavaScript將function關鍵字昨晚函式宣告的開始,但函式宣告後面不能跟圓括弧,所以function(){......}();會出錯。
要將函式宣告轉換為函數運算式,需為函式宣告加一對圓括弧:
複製代碼 代碼如下:
(function(){
//塊級範圍
})();