在編程語言中,無論是面向過程的C,兼備面過程和對象的c++,還是面向對象的編程語言,如java,.net,php等,函數均扮演著重要的角色。當然,在面向對象編程語言JavaScript中(嚴格來說,JS屬於弱面向對象編程語言),函數(function)更扮演著極其重要的角色和占有極其重要的地位。在本 ...
在編程語言中,無論是面向過程的C,兼備面過程和對象的c++,還是面向對象的編程語言,如java,.net,php等,函數均扮演著重要的角色。當然,在面向對象編程語言JavaScript中(嚴格來說,JS屬於弱面向對象編程語言),函數(function)更扮演著極其重要的角色和占有極其重要的地位。在本篇文章中,不論述什麼是JS,JS解決什麼問題等之類問題,而是重點闡述JS中的函數(function)。
一 什麼是javascript函數
1.函數定義
關於函數的定義,我們先從兩個角度來思考:數學角度和編程語言角度。
(1)數學角度:在數學領域,關於“函數”二字,再熟悉不過,如三角函數,反三角函數,冪函數,對數函數,指數函數,微積分函數等;
(2)編程角度:在編程領域,大家最熟悉且最先接觸的應該是"Main函數"了,除此外,如日期函數(Date),數學函數(Math)等,當然除了內置函數外,還包括用戶自定義函數;
綜合1,2點,我們不難發現,函數的定義應該是這樣的:
函數是解決某類問題的集合,是某類問題的高度抽象,它具有一定的通用性和復用性。
2.js中兩種經典函數定義
在Javascript中,存在兩種經典的函數定義方式:函數聲明式和函數表達式
(1)函數聲明式
1 //定義兩個數相加函數 2 function AddNum(num1, num2) { 3 return num1 + num2; 4 }
(2)函數表達式
1 //定義兩個數相加函數 2 var AddFun=function AddNum(num1, num2) { 3 return num1 + num2; 4 }
註意,在用函數表達式定義時,一般採用匿名函數定義,即如下:
1 //定義兩個數相加函數 2 var AddFun=function (num1, num2) { 3 return num1 + num2; 4 }
Question:為什麼函數表達式用匿名函數,而函數聲明式不用匿名?
答:因為函數表達式調用時,使用的是函數表達式名,不需要函數名,因此函數名可以匿名,而函數聲明式調用時,使用函數名調用,因此不用匿名函數;
聲明式調用1:
1 //定義兩個數相加函數 2 function AddNum(num1, num2) { 3 return num1 + num2; 4 } 5 6 7 console.log(Add(10,20));//30
聲明式自調用:
自調用只存在函數聲明式中,也叫立即調用,不能在在函數表達式中調用。
1 (function AddNum(num1, num2) { 2 console.log(num1 + num2); 3 })(10,20);//30
聲明式調用3:錯誤調用方式
請大家想想,這種調用方式為什麼會錯?
1 function (num1, num2) { 2 return num1 + num2; 3 } 4 5 console.log((10,20));
調用結果:
表達式調用1:推薦寫法
1 //定義兩個數相加函數 2 var AddFun=function (num1, num2) { 3 return num1 + num2; 4 } 5 6 console.log(AddFun(10,20));//30
表達式調用2:不推薦寫法
1 //定義兩個數相加函數 2 var AddFun=function AddNum(num1, num2) { 3 return num1 + num2; 4 } 5 6 console.log(AddFun(10,20));//30
表達式調用3:錯誤調用方式
請大家想想,這種調用方式為什麼會錯?
1 //定義兩個數相加函數 2 var AddFun=function AddNum(num1, num2) { 3 return num1 + num2; 4 } 5 6 console.log(AddNum(10,20));//30
調用結果:
3.變數
在JavaScript編程語言中,變數的定義是通過var關鍵字來定義的(若變數不通過var定義,則為全局變數,但不推薦這麼做),與其他編程語言一樣,變數也分為兩大類,即局部變數和全局變數。
(1)局部變數:作用域為其所在的函數;
(2)全局變數:作用域為整個過程;
(3)變數作用域:JS中的變數作用域是通過this指針,從當前的作用域開始,從當前作用域由內向外查找,直到找到位置,這裡分為幾個邏輯:
a.從當前作用域由內向外查找,若找到,就停止查找,否則,繼續查找,直到查到window全局作用域為止;
b.當內部作用域變數名與外部作用域變數名相同時,內部作用域的覆蓋外部作用域。
我們來看一個例子:
1 var dateTime='2018-09-16'; 2 function GetUserInfo(){ 3 var age=120; 4 var name="Alan_beijing"; 5 function Say(){ 6 var name="老王"; 7 var address="shanghai"; 8 console.log(address+"-"+name+"-"+age+"-"+dateTime);//shanghai-老王-2018-06-05 9 } 10 return Say(); 11 } 12 13 14 GetUserInfo();//shanghai-老王-120-2018-09-16
來分析一下變數及其作用域:
如上圖,有4個作用域,當函數執行如下語句時,發生如下過程:
1 console.log(address+"-"+name+"-"+age+"-"+dateTime);
a.js當前this環境作用域為4作用域;
b.this指針尋找變數:addresss,name,age,dateTime,從當前作用域向外作用域逐層尋找,知道尋找到變數值為止,若尋找到最外層作用域任然沒找到,則該變數返回undefined;
c.當內外層變數相同時,內層變數覆蓋外層變數,如4作用域的name覆蓋3作用域的name;
4.函數聲明式定義存在的問題
在js中,存在聲明提前問題,看看如下例子。
1 var globleName="Alan_beijing"; 2 function Say(){ 3 console.log(localName); // undefined,不報錯,是因為變數聲明提前 4 var localName="Alan"; 5 console.log(localName);// Alan 6 }
看過如上代碼,你可能會問,函數執行到console.log(localName); 時,應該報錯,因為localName未定義。
如果在後端語言,如java,.net中,可能會報錯,但是在js中,卻不會,不報錯的原因是:在js中存在聲明提前。
如上代碼相當於如下代碼:
1 var globleName="Alan_beijing"; 2 function Say(){ 3 var localName; 4 console.log(localName); 5 localName="Alan"; 6 console.log(localName); 7 }
二 函數幾大關鍵點
1.匿名函數
匿名函數,顧名思義,就是沒名字的的函數,我們來看看如下兩個例子:
函數表達式
1 //定義兩個數相加函數 2 var AddFun=function (num1, num2) { 3 return num1 + num2; 4 }
立即執行函數
(function AddNum(num1, num2) { console.log(num1 + num2); })(10,20);//30
從如上,不難看出,匿名函數主要用域函數表達式和立即執行函數。
2.閉包
閉包的根源在於變數的作用域問題。
我們先來考慮這樣一個問題,假設在面向對象編程語言中,某個方法的變數被定義為私有變數,其他函數要獲取訪問該變數,.net怎麼處理?
方法一:構造函數
方法二:單例模式
同樣地,在js中,同樣存在外部函數調用內部函數變數問題,js運用的技術就叫做閉包。
所謂閉包,就是將不可訪問的變數作為函數返回值的形式返回來,從而實現函數外部訪問函數內部變數目的。
1 //閉包 2 function GetName() { 3 var name = "Alan_beijing"; 4 var age = function () { 5 var age = 30; 6 return age; 7 } 8 return name + age; 9 }
3.js多態問題(重載問題)
在面向對象編程語言,如.net中,實現多態的方式大致有如下:
a.介面
b.抽象類
c.虛方法
d.方法重載
然而,在js中,沒有面向對象之說(OO),那麼js是如何實現多態的呢?根據方法實際傳遞的參數來決定。
1 //重載 2 function SetUserInfo(userName, age, address, tel, sex) { 3 console.log(arguments.length);//4 4 } 5 6 SetUserInfo('Alan_beijing',44,'china-shanghai','xxxx');
從如上可以看出,傳遞多少個參數,就接收多個參數,如果在現象對象編程語言中實現該功能,至少需要寫一堆代碼,這也是體現js強大之一。
4.遞歸
來看看一個遞歸階乘函數
1 //遞歸 2 function factorial(num) { 3 if (num < 1) { 4 return 1; 5 } else { 6 return num * arguments.callee(num-1); 7 } 8 }
如果是.net,我們一般會這樣寫
1 //遞歸 2 function factorial(num) { 3 if (num < 1) { 4 return 1; 5 } else { 6 return num * factorial(num-1); 7 } 8 }
然而,這樣寫,卻會存在異常情況
1 var factorial1 = factorial; 2 factorial = null;//將factorial變數設置為null 3 console.log(factorial1(4));//出錯
5.原型和原型鏈
面向對象編程語言的顯著特征之一是面向對象,然而,在js中,沒有對象,那麼js是如何面向對象的功能的呢(封裝,繼承,多態)?當然是通過原型和原型鏈來實現的。
大家都比較怕原型和原型鏈,其實很簡單,它的功能相當於面向對象的繼承,主要解決繼承和復用。
介於篇幅有限,餘下的內容,將在下篇文章闡述.....
三 參考文獻
【01】JavaScript 高級程式設計(第三版) (美)Nicholas C.Zakas 著 李松峰 曹力 譯
【02】JavaScript 權威指南 (第6版) David Flanagan 著