JavaScript之函數(上)


       在編程語言中,無論是面向過程的C,兼備面過程和對象的c++,還是面向對象的編程語言,如java,.net,php等,函數均扮演着重要的角色。當然,在面向對象編程語言JavaScript中(嚴格來說,JS屬於弱面向對象編程語言),函數(function)更扮演着極其重要的角色和占有極其重要的地位。在本篇文章中,不論述什么是JS,JS解決什么問題等之類問題,而是重點闡述JS中的函數(function)。

一  JavaScript函數

(一)何為函數

關於函數的定義,我們先從兩個角度來思考:數學角度和編程語言角度。

1.數學角度:在數學領域,關於“函數”二字,再熟悉不過,如三角函數,反三角函數,冪函數,對數函數,指數函數,微積分函數等;

2.編程角度:在編程領域,大家最熟悉且最先接觸的應該是"Main函數"了,除此外,如日期函數(Date),數學函數(Math)等,當然除了內置函數外,還包括用戶自定義函數;

      綜合1,2點,可以將函數定義如下:

函數是解決某類問題的集合,是某類問題的高度抽象,它具有一定的通用性和復用性。

(二)定義函數

在Javascript中,存在三種經典的函數定義方式:函數聲明式,函數表達式和函數構造式

1.函數聲明式

1 //定義兩個數相加函數
2     function AddNum(num1, num2) {
3         return num1 + num2;
4     }

注意:函數聲明式,不能用匿名函數,如下方式定義錯誤的

1 function (num1, num2) {
2         return num1 + num2;
3     }

2.函數表達式

1  //定義兩個數相加函數
2     var AddFun=function AddNum(num1, num2) {
3         return num1 + num2;
4     }

注意:函數表達式一般用匿名函數,因為調用時,不用函數名,因此,如上調用也可寫成如下:

1 var AddFun = function(num1, num2) {
2         return num1 + num2;
3     }

3.函數構造式

1 var sum = new Function('num1', 'num2', 'return num1 + num2')
2  console.log(sum(10,20));//30

 (三)函數調用

 在JavaScript中,函數的調用采用“函數名(參數)”的格式來進行調用。

需要注意的是,JavaScript函數調用與后端語言函數調用(如java,.net等)存在細微差別,即JavaScript沒有函數重載,參數的參數個數,由實參決定,而不是由形參決定。

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

注意:自調用一般用匿名函數,因為在調用時,不需要函數名,因此,也可寫成如下方式:

1  (function (num1, num2) {
2         return num1 + num2;
3     })(10, 20);//30

2.表達式調用

1 //定義兩個數相加函數
2  var AddFun = function (num1, num2){
3            return num1 + num2;
4  }
5      
6  console.log(AddFun(10, 20));//30

二 函數變量

在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,從當前作用域向外作用域逐層尋找,直到尋找到變量為止,若尋找到最外層作用域任然沒找到,則會出錯,提示該變量未聲明;

c.當內外層變量相同時,內層變量覆蓋外層變量,如4作用域的name覆蓋3作用域的name;

 

三   函數聲明式定義存在的問題

 在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 著

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM