1. 程式人生 > >【JavaScript系列】淺談JavaScript之函式(一)

【JavaScript系列】淺談JavaScript之函式(一)

       在程式語言中,無論是面向過程的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 著