函數表達式
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
在非嚴格模式,使用遞歸函數時,用argument.callee代替函數名更保險
在嚴格模式下,使用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(){ //塊級作用域 })();
