1. 程式人生 > >JavaScript Function 函式深入總結

JavaScript Function 函式深入總結

整理了JavaScript中函式Function的各種,感覺函式就是一大物件啊,各種知識點都能牽扯進來,不單單是 Function 這個本身原生的引用型別的各種用法,還包含執行環境,作用域,閉包,上下文,私有變數等知識點的深入理解。

函式中的return

return 語句可以不帶有任何返回值,在這種情況下( return; 或函式中不含 return 語句時),函式在停止執行後將返回 undefiend 值。這種用法一般在需要提前停止函式執行而又不需要返回值的情況下。
return false 可以阻止元素的預設事件。
return 返回的是其所在函式的返回值
function n(){
(function(){
return 5;
})();
}
n();// undefined
//立即執行匿名函式中的return語句其實是返回給它所在的匿名函式的。

function n(){
var num= (function(){
return 5;
})();
console.log(num);
}
Function型別

函式實際上是物件,每個函式實際上都是 Function 型別的例項。而且與其他引用型別一樣具有屬性和方法。函式名實際上是一個指向記憶體堆中某個函式物件的指標。

定義函式的方式

函式宣告
function sum(num1,num2){
return num1+num2;
}
函式表示式
var sum=function(num1,num2){
return num1+num2;
};
定義了一個變數 sum 並將其初始化為一個函式,注意到 function 關鍵字後面並沒有函式名,這是因為在使用函式表示式定義函式,沒必要使用函式名,通過變數 sum 即可引用函式。還要注意函式末尾有個分號,就像宣告其他變數一樣。

new 建構函式,雖然這種用法也是函式表示式,但該用法不推薦。因為這種語法會導致解析兩次程式碼(第一次是解析常規的ECMAScript程式碼,第二次是解析傳入建構函式中的字串),影響效能。
使用 Function 建構函式,建構函式可以接受任意數量的引數,但最後一個引數始終都被看成是函式體,前面的引數則枚舉出了新函式的引數。
var sum=new Function(‘num1’,’num2’,’return num1+num2;’);
sum;//
function anonymous(num1,num2
/**/) {
return num1+num2;
}
當使用不帶圓括號的函式名是訪問函式指標,而非呼叫函式。

理解引數

ECMAScript中所有引數傳遞的都是值(即使是引用也是傳遞的地址值,不是引用傳遞引數(可參考JavaScript傳遞引數是按值傳遞還是按引用傳遞))。ECMAScript函式不介意傳遞進來多少個引數,也不在乎傳進來的引數是什麼資料型別。之所以這樣,是因為ECMAScript中的引數在內部是用一個數組表示的。函式接收到的始終都是這個陣列,而不關心陣列中包含哪些引數。在函式體內,可以通過 arguments 物件來訪問這個陣列。從而獲取傳遞給函式的每個引數。

function func(){
console.log(Object.prototype.toString.call(arguments));
}

func();// [object Arguments]
關於 arguments 的行為,它的值永遠與對應命名引數的值保持同步。因為 arguments 物件中的值會自動反映到對應的命名引數。所以修改 arguments[1] ,也就修改了 num2 。不過這並不是說讀取這兩個值會訪問相同的記憶體空間,它們的記憶體空間是獨立的,但他們值會同步(WHY??),要是JavaScript能直接訪問記憶體就好了驗證一下。
但如果只傳入了一個引數,那麼 arguments[1] 設定的值不會反映到命名引數中,這是因為 arguments 物件的長度是由傳入引數個數決定的,不是由定義函式時的命名引數個數決定的,沒有傳遞值的命名引數將自動被賦予 undefiend 值,這就跟定義了變數但沒初始化一樣。
function doAdd(num1,num2){
console.log(arguments.length);
console.log(num2)
arguments[1]=10;
console.log(num2);
}
doAdd(5,0);//2 0 10

doAdd(5);//1 undefiend undefined
沒有過載

ECMAScript函式不能像傳統意義上那樣實現過載,而在其他語言中(Java),可以為一個函式編寫兩個定義,只要這兩個定義的簽名(接收引數的型別和數量)不同即可。

不能實現過載的原因:

ECMAScript函式沒有簽名,因為其引數是由包含零個或多個值的陣列來表示的。沒有函式簽名,真正的過載是不可能做到的。在ECMAScript中定義兩個名字相同的的函式,則該名字只屬於後定義的函式。如何實現類似於Java中的過載呢,其實可以通過判斷傳入函式的引數型別和個數來做出不同響應。
function reload(){
if(arguments.length==0){
console.log(‘沒傳參’);
}else if(arguments.legth==1){
console.log(‘傳了一個引數’);
}
}
深入理解:將函式名想象為指標,也有助於理解為什麼ECMAScript中沒有函式過載的概念。
function add(){
return 100;
}
function add(num){
return num+200;
}

//實際上和下面程式碼沒什麼區別
function add(){
return 100;
}
add=function(num){
return num+200;
}
函式宣告和函式表示式

實際上解析器在向執行環境中載入資料時,對函式宣告和函式表示式並非一視同仁。

JavaScript執行機制淺探 中瞭解到對於解釋型語言來說,編譯步驟為:

詞法分析(將字元流轉換為記號流,是一對一的硬性翻譯得到的是一堆難理解的記號流)
語法分析(這裡進行所謂的變數提升操作,其實我覺得是把這些提升的變數儲存在語法樹中。要構造語法樹,若發現無法構造就會報語法錯誤,並結束整個程式碼塊的解析)
之後可能有語義檢查,程式碼優化等。得到語法樹後就開始解釋執行了。解釋性語言沒有編譯成二進位制程式碼而是從語法樹開始執行。
解析器會先讀取函式宣告,並使其在執行任何程式碼之前可用。至於函式表示式,則必須等到執行階段才會被真正賦值。什麼意思呢?雖然兩者都進行了變數提升,待真正執行時構造活動物件從語法樹種取宣告新增到執行環境中,但一個是函式提升,一個是變數提升。

//函式宣告
console.log(func);//function func(){}
function func(){

}

//函式表示式
console.log(func1);// undefined
var func1=function(){};
console.log(func1);// function(){}
作為值的函式

因為ECMAScript中的函式名本身就是變數,所以函式也可以作為值來使用。不僅可以像傳遞引數一樣把一個函式傳遞給另一個函式,而且可以將一個函式作為另一個函式的結果返回。

function callSomeFunction(someFunction,someArgument){
return someFunction(someArgument);
}

function concated(str){
return “Hi “+str;
}

callSomeFunction(concated,’xx’);// ‘Hi xx’
從一個函式中返回另一個函式的應用:假設有一個物件陣列,想要根據某個物件屬性對陣列進行排序,但傳給 sort() 方法的比較函式要接收兩個引數,即要比較的值。我們需要一種方式來指明按照哪個屬性來排序。我們可以定義一個函式它接收一個屬性名,然後根據這個屬性名來建立一個比較函式。預設情況下, sort 函式會呼叫每個物件的 toString() 方法以確定它們的次序。

function createCompare(property){
return function(obj1,obj2){
var value1=obj1[property],
value2=obj2[property];
if(value1