輕鬆學習JavaScript八:JavaScript函數
函數是一組可以隨時隨地啟動並執行語句,函數作為ECMAScript的核心是很重要的。函數是由事件驅動的或者當它被
調用時執行的可重複使用的代碼塊。也就是函數是定義一次但卻可以調用或執行任意多次的一段JavaScript代碼。函
數有時會有參數,即函數被調用時指定了值的局部變數。函數常常使用這些參數來計算一個傳回值,這個值也成為函
數調用運算式的值。
一函式宣告
函數對於任何語言來說都是一個核心的概念。通過函數可以封裝任意多條語句,而且可以在任何地方,任何時候
調用執行。JS中的函數使用function關鍵字來聲明,後跟一組參數以及函數體。
函數的基本文法是這樣的:
function functionName(arg0, arg1, ... argN) { statements}
ECMAScript規定的函式宣告方式有三種:
(1)普通函式宣告
function box(num1,num2){ return num1+num2;}
(2)使用變數初始化什聲明函數
var box=function(num1,num2){ return num1+num2;}
(3)使用Function建構函式聲明
vax box=new Function('num1','num2','num1+num2');
二函數的類型及函數的調用
ECMAScript文法規定了
(1)無參數的函數:函數的聲明的時候沒有參數,調用函數的時候直接使用即可。
function box(){ document.write("我是中國人!");}box();//函數調用
啟動並執行結果為:我是中國人!
(2)帶參數的函數:函數的聲明的時候同時定義了參數變數,參數可以是多個。
function box(name,age) { document.write("你的姓名是:"+name+"你的年齡是:"+age);}box("張三","24");//函數調用
啟動並執行結果為:你的姓名是:張三
你的年齡是:24
(3)帶有傳回值的函數
帶參數和無參數的函數,都沒有定義傳回值,而是調用後直接執行的,實際上,任何函數都可以通過return語句
跟後面的要返回的值來實現傳回值
1無參數的函數
function box(){ return "我是中國人!";}document.write(box());
同上面的輸出結果:我是中國人!
2帶參數的函數
function box(name,age){ return "你的姓名是:"+name+""+"你的年齡是:"+age;}document.write(box("張三","24"));//函數調用document.write(""); var demo=box("李四","23");//也可以重新賦值新的函數 document.write(demo);
啟動並執行結果為:
(4)作為值的函數(比較特殊)
首先我們來看一個函數作為常規的變數的例子:
function box(sum,num){ return sum+num;//這裡傳遞的是函數的傳回值和普通的變數一樣}function sum(num){ return num+10;}var result=box(sum(10),10);document.write("result="+result);
頁面的輸出結果為:result=30
下面則傳遞的是函數,仔細和上面的區分:
function box(sum,num){ return sum(num);//這裡傳遞的是函數}function sum(num){ return num+10;}var result=box(sum,10);document.write("result="+result);
頁面的輸出結果為:result=20
三函數的內部屬性
在函數內部,有兩個特殊的對象:arguments對象和this對象。arguments對象是類數組對象,包含著傳入函數中
的所有參數,主要用途是儲存函數參數,主要的屬性有length,這個屬性是動態判斷函數有多少個參數。但這個對
象還有一個名叫callee的屬性,該屬性是一個指標,指向擁有這個arguments對象的函數。
(1)arguments對象的length屬性
JS函數不介意傳遞進來多少參數,也不會因為參數不統一而錯誤。實際上,函數體內可以通過arguments對象來
接收傳遞進來的參數。
我們先來看一個我們在函數傳遞參數遇到的問題:函式宣告時並不知道要定義多少個參數,在調用函數卻出現多
出的或不足的問題。
function box(){ return arguments[0]+"|"+arguments[1];}document.write(box(1,2,3,4,5,6));
輸出的結果為:1|2。因此輸出的顯然與我們想要做的不符,那麼怎麼解決呢?
有了arguments對象的length屬性我們就能可以得到參數的數量,避免上面的錯誤出現。
function box(){ return arguments.length;}document.write(box(1,2,3,4,5,6));
輸出:6
我們還可以利用length屬性來智能的判斷有多少參數,然後把參數進行合理的應用,比如,實現一個加法運算,
將所有傳進來的數字累加,而數位個數又不確定。
function box(){ var sum=0; if(arguments.length==0) { return sum; } for(var i=0;i
輸出:21
(2)arguments對象的callee屬性
還是來說問題:對於遞迴的問題我們很熟悉了,JS中也不例外
function box(num){ if(num<=1){return 1; } else{ return num*box(num-1);//遞迴}}document.write(box(4));
輸出:24
對於階乘函數一般要用到遞迴演算法,所以函數內部一定對調用自身,如果函數名不改變是沒有問題的,但一旦改
變函數名,內部的自身調用需要逐一修改。為瞭解決這個問題,可以使用arguments.callee來代替。
function box(num){ if(num<=1) { return 1; } else { return num*arguments.callee(num-1)//遞迴 }}document.write(box(4));
輸出:24
(3)this對象
函數內部另一個特殊的對象時this,其行為與Java和C#中的this大致相似,換句話說,this引用的是函資料以執行
操作的對象,或者說函數調用語句所處的那個範圍。當在全域範圍中調用函數時,this對象引用的就是
window(window是一個對象,是JavaScript中最大的對象,是最外圍的對象)。
var color="紅色";//這裡的color是全域變數,並且這個變數是window的屬性document.write(window.color+"");document.write(this.color+"");var box={ color:"藍色",//這裡的color是box下的屬性,是局部變數 sayColor:function(){ return this.color;//此時的this只能是box中的color }};document.write(box.sayColor()+"");//局部的document.write(this.color);//全域的
啟動並執行結果為:
四函數屬性和方法
(1)JavaScript中的函數是對象,因此函數也有屬性和方法。每個函數都包含兩個屬性:length和prototype。其
中,length屬性工作表示函數希望接受的具名引數的個數。
function box(num1,num2){ return num1+num2;}document.write(box.length);輸出的結果;2
對於prototype屬性,它是儲存所有執行個體方法的真正所在,也就是原型。這個屬性我們先不做過多的介紹。
prototype屬性下有兩個方法:apply()和call(),每個函數都包含這兩個非繼承而來的方法。這兩個方法的用途都在特定
的範圍中調用函數,實際上等於設定函數體內this對象的值。
function box(num1,num2){ return num1+num2;}function sayBox(num1,num2){ return box.apply(this,[num1,num2]);//this表示範圍,這裡是window,[]表示box所需的參數}function sayBox2(num1,num2){ return box.apply(this,arguments);//arguments對象表示box所需的參數}document.write(sayBox(10,10)+"");document.write(sayBox2(10,10));
輸出的結果為:20
20
(2)call()方法和apply()方法延伸
call()方法和apply()方法相同,它們的區別僅僅在於接收參數的方式不同。對於call()方法而言,第一個參數是作用
域,沒有變化,變化的只是其餘參數都是直接傳遞給函數的。
function box(num1,num2){ return num1+num2;}function callBox(num1,num2){ return box.call(this,num1,num2);//區別apply()方法}document.write(callBox(10,10));
輸出的結果為:20
call()方法和apply()方法真正的作用是擴充函數賴以啟動並執行範圍
var color="紅色";//全域變數var box={ color:"藍色",//局部變數};function sayColor(){ return this.color;}document.write(sayColor()+"");//範圍在Windowdocument.write(sayColor.call(this)+"");//範圍在Window下document.write(sayColor.call(window)+"");//範圍在Window下document.write(sayColor.call(box));//範圍在box下,對象冒充輸出的結果為:
使用call()方法或者apply()方法來擴充範圍的最大好處就是對象不需要與方法發生任何耦合關係。也就是說,
box對象和sayColor()方法之間不會有多餘的關聯操作,比如;box.sayColor=sayColor;
五ECMAScript閉包
ECMAScrip最易讓人誤解的一點是,它支援閉包。閉包,指的是詞法表示包括不被計算的變數的函數,也就是
說,函數可以使用函數之外定義的變數。
其實我在前面的博文已經使用到了閉包,比如在輕鬆學習JavaScript七:JavaScript的流程式控制制語句中使用的變數
time就是全域變數,函數myFunction()使用這個全域變數,並不是函數本身定義的。還是看一下那個執行個體吧:
var time=new Date().getHours(); document.write("當前北京時間:"+time); function myFunction() { var x=""; if (time<20) { x="Good day"; } document.getElementById("demo").innerHTML=x; }
(1)簡單的閉包執行個體
在ECMAScript中使用全域變數是一個簡單的閉包執行個體。請思考下面這段代碼輸出的結果是什麼:
var sMessage = "hello world";function sayHelloWorld() { document.write(sMessage);}sayHelloWorld();
在上面這段代碼中,指令碼被載入記憶體後,並沒有為函數sayHelloWorld()計算變數sMessage的值。該函數捕
sMessage的值只是為了以後的使用,也就是說,解釋程式知道在調用該函數時要檢查sMessage的值。sMessage將
在函數調用sayHelloWorld()是在(最後一行)被賦值,顯示訊息"hello world"。
(2)複雜的閉包執行個體
在一個函數中定義另一個會使閉包變得更加複雜。例如:
var iBaseNum = 10;//全域變數function addNum(iNum1, iNum2) { function doAdd() { return iNum1 + iNum2 + iBaseNum; } return doAdd();}document.write(addNum(10,10));
這裡,函數addNum()包括函數doAdd()(閉包)。內建函式是一個閉包,因為它將擷取外部函數的參數iNum1和
iNum2以及全域變數iBaseNum的值。 addNum()的最後一步調用了doAdd(),把兩個參數和全域變數相加,並返回它
們的和。這裡要掌握的重要概念是,doAdd()函數根本不接受參數,它使用的值是從執行環境中擷取的,因此輸出的
結果為:30。
可以看到,閉包是 ECMAScript 中非常強大多用的一部分,可用於執行複雜的計算。就像使用任何進階函數一樣
,使用閉包要小心,因為它們可能會變得非常複雜。
如果想要學習更加詳細的JS函數的知識,參考ECMAScript函數。