這篇文章,我將就以下幾個方面來認識JavaScript中的函數。
JavaScript中最有意思的恐怕是函數了,因為和其他語言不同,在JavaScript中,每個函數都是Function類型的實例,而我們知道:Object是一個基礎類型,其他所有類型都是從Object繼承了基本的行為。也就是說Function也是從Object引用類型繼承而來的,那麼作為Function類型的實例,函數也就是對象這一點就不難理解了。
那麼如何定義一個函數呢?一般來說,有三種方式可以來定義函數。
第一:函數聲明。這種語法和其他語言的形式一樣,是我們最為常見的一種方式。如下:
function add(num){ return num+10; }
第二:函數表達式。如下:
var add=function(num){ return num+10; };
我們可以注意到函數表達式的定義函數方法把函數看作了一個表達式,因此最後要以分號;結尾,並且在function之後沒有函數名,那麼怎麼調用呢?實際上,通過add即可引用了。(實際上,這裡的add是全局對象global在浏覽器中表現為window對象的一個屬性或方法)
第三:使用Function構造函數。如下:
var add=new Function("num","return num+10");
Function構造函數可以接收任意多的參數,其中最後一個是函數體,前面所有的是函數的參數。這種方法是我們所不推薦的,因為它會導致解析兩次代碼(第一次是解析常規的ECMAScript代碼,第二次是解析傳入構造函數中的字符串),從而影響了性能。但是這種方法有利於我們理解:函數是對象,函數名是指針。
由於我們不推薦第三種方法來創建函數,並且在實際中也用的很少。那麼前兩種方法又有什麼區別呢?
實際上,區別僅僅在於是否函數聲明的方法會使得代碼在開始執行之前,解析器就已經通過了函數聲明提升讀取並將函數添加到了執行環境中去,於是在代碼求知識,JavaScript引擎會將它們放到源代碼樹的頂部,而函數表達式則不會。這裡可能不好理解,看下面例子:
a
function say(){ console.log("I like coding"); } say();
此時在控制台輸出了I like coding.
b
var say=function (){ console.log("I like coding"); } say();
同樣,這時在控制台也輸出了I like coding.
c
say(); function say(){ console.log("I like coding"); }
這裡我們將say()這個調用函數的語句放在最上面了,同樣控制台也輸出了I like coding.
d
say(); var say=function (){ console.log("I like coding"); };
然而,這裡卻出現了錯誤。讓我們捕獲以下錯誤。
try{ say(); var say=function (){ console.log("I like coding"); }; }catch(err){ console.log("錯誤是:"+err); }
控制台提示:錯誤是:TypeError: say is not a function。以上代碼之所以會在運行期間產生錯誤,是因為函數沒有位於一個函數聲明中,而是位於一個初始化的語句中,這樣如何沒有執行到該語句,那麼變量sum就不會保存有對函數的引用。而使用函數聲明,JavaScript引擎將函數聲明提升到了最頂端,那麼函數名sum就保存了對函數的引用。
為了更深刻的理解函數是對象,函數名是指針,我們可以以下面的例子講解:
function add(num){ return num+10; } console.log(add(10));//20 var addCopy=add; //這時我們把add這個指針賦值給addCopy,於是addCopy也指向了同一個函數對象 console.log(addCopy(10));//20 sum=null; //null 的一大作用就是用於保存空的對象,這時sum指向了一個空的對象 console.log(sum(10));//Uncaught TypeError: sum is not a function(…) sum是一個空對象,因此會出現語法錯誤 console.log(addCopy(10));//20 而addCopy指向了那個函數對象,便可以得到正確的答案
也正是因為函數是對象,其函數名(指針)可以有多個(指向的是同一個對象),因此也不難理解函數沒有重載。
一般,函數中的參數都是變量。而因為函數名是指針,也是變量,因此我們就可以把函數作為值來使用了。
如下:
function onefunc(antherfunc,argum){ return antherfunc(argum); } function antherfunc(str){ return str+"I am js"; } console.log(onefunc(antherfunc,"ha "));//ha I am js
除此之外,一個函數也可以作為另一個函數的結果返回。如下:
function createComparisonFunction(propName){ return function(object1,object2){ //這是一個比較函數,雖然有形參,但是不需要傳遞實參,只是為了便於下面調用 var value1=object1[propName]; var value2=object2[propName]; //這裡使用方括號操作符的好處在於:當定義一個對象數組時,裡面的對象是用對象字面量方法來創建的,於是屬性名可以加引號,這時屬性名即使不規范也不會影響結果,應當注意的是,這時,最後調用的時候還是需要使用方括號調用。。 if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } }; //注意:雖然這裡不用分號也可以運行,但是最好寫上,這樣才規范。 } var data=[{name:"zhuzhen",age:21},{name:"heting",age:18}]; //這裡表示按照何種屬性排序 data.sort(createComparisonFunction("name")); console.log(data[0].name);//heting data.sort(createComparisonFunction("age")); console.log(data[0].age);//18
我們可以先來總結一下函數一共有哪些屬性和方法且函數有哪些對象。
函數的屬性:length prototype caller
函數的方法:繼承自Object的有toString0(),valueOf(),toLocaleString()。函數自身添加的方法有call() apply() bind()
函數的內部對象:this arguments(它還有一個callee屬性)