JavaScript高級之函數的四種調用形式

主要內容

 

分析函數的四種調用形式

弄清楚函數中this的意義

明確構造函對象的過程

學會使用上下文調用函數

  瞭解函數的調用過程有助於深入學習與分析JavaScript代碼. 本文是JavaScript高級這 個系列中的第三篇文章,主要介紹JavaScript中函數的四種使用形式. 

  在JavaScript中,函數是一等公民,函數在JavaScript中是一個數據類型,而非像C#或 其他描述性語言那樣僅僅作為一個模塊來使用. 函數有四種調用模式,分別是:函數調用形

式、方法調用形式、構造器形式、以及apply形式. 這裡所有的調用模式中,最主要的區別在

於關鍵字 this 的意義. 下面分別介紹這個幾種調用形式.

 

一、函數調用形式

函數調用形式是最常見的形式,也是最好理解的形式. 所謂函數形式就是一般聲明函數 後直接調用即是. 例如:

 

1 // 聲明一個函數,並調用

2 function func() {

3     alert("Hello World");

4 }

5 func();

或者

 

1 // 使用函數的Lambda表達式定義函數,然後調用

2 var func = function() {

3     alert("你好,傳智播客");

4 };

5 func();

這兩段代碼都會在瀏覽器中彈出一個對話框,顯示字符串中的文字. 這個就是函數調用.

 

  可以發現函數調用很簡單,就是平時學習的一樣. 這裡的關鍵是,在函數調用模式中, 函數裡的 this 關鍵字指全局對象,如果在瀏覽器中就是 window 對象. 例如:

 

1 var func = function() {

2     alert(this);

3 };

4 func();

此時,會彈出對話框,打印出 [object Window]

 

 

 

二、方法調用模式

  函數調用模式很簡單,是最基本的調用方式. 但是同樣的是函數,將其賦值給一個對 象的成員以後,就不一樣瞭. 將函數賦值給對象的成員後,那麼這個就不在稱為函數,而

應該叫做方法. 例如:

 

復制代碼

1 // 定義一個函數

2 var func = function() {

3     alert("我是一個函數麼?");

4 };

5 // 將其賦值給一個對象

6 var o = {};

7 o.fn = func; // 註意這裡不要加圓括號

8 // 調用

9 o.fn();

復制代碼

此時,o.fn 則是方法,不是函數瞭. 實際上 fn 的方法體與 func 是一模一樣的,但是這

裡有個微妙的不同. 看下面的代碼:

 

1 // 接上面的代碼

2 alert(o.fn === func);

打印結果是 true ,這個表明兩個函數是一樣的東西. 但是修改一下函數的代碼:

 

復制代碼

 1 // 修改函數體

 2 var func = function() {

 3     alert(this);

 4 };

 5 var o = {};

 6 o.fn = func;

 7 // 比較

 8 alert(o.fn === func);

 9 // 調用

10 func();

11 o.fn();

復制代碼

  這裡的運行結果是,兩個函數是相同的,因此打印結果是 true. 但是由於兩個函數的調用

是不一樣的,func 的調用,打印的是 [object Window],而 o.fn 的打印結果是 [object Object].

 

  這裡便是函數調用與方法調用的區別. 函數調用中,this 專指全局對象 window,而 在方法中 this 專指當前對象. 即 o.fn 中的 this 指的就是對象 o.

 

 

 

三、構造器調用模式

  同樣是函數,在單純的函數模式下,this 表示 window;在對象方法模式下,this 指 的是當前對象. 除瞭這兩種情況,JavaScript 中函數還可以是構造器. 將函數作為構造器

來使用的語法就是在函數調用前面加上一個 new 關鍵字. 如代碼:

 

復制代碼

 1 // 定義一個構造函數

 2 var Person = function() {

 3     this.name = "傳智播客";

 4     this.sayHello = function() {

 5         alert("你好,這裡是" + this.name);

 6     };

 7 };

 8 // 調用構造器,創建對象

 9 var p = new Person();

10 // 使用對象

11 p.sayHello();

復制代碼

  上面的案例首先創建一個構造函數Person,然後使用構造函數創建對象p. 這裡使用 new 語法. 然後在使用對象調用sayHello()方法. 這個使用構造函數創建對象的案例比較簡單. 

從案例可以看到,此時 this 指的是對象本身. 

  除瞭上面簡單的使用以外,函數作為構造器還有幾個變化. 分別為:

 

   1、 所有需要由對象使用的屬性,必須使用 this 引導;

   2、 函數的 return 語句意義被改寫,如果返回非對象,就返回this;

 

3.1 構造器中的 this

  我們需要分析創建對象的過程,方能知道 this 的意義. 如下面代碼:

 

1 var Person = function() {

2     this.name = "傳智播客";

3 };

4 var p = new Person();

這裡首先定義瞭函數 Person,下面分析一下整個執行:

  1、 程序在執行到這一句的時候,不會執行函數體,因此 JavaScript 的解釋器並不知道這個

    函數的內容.

 

  2、 接下來執行 new 關鍵字,創建對象,解釋器開辟內存,得到對象的引用,將新對象的引

    用交給函數. 

  3、 緊接著執行函數,將傳過來的對象引用交給 this. 也就是說,在構造方法中,this 就是

    剛剛被 new 創建出來的對象.

 

  4、 然後為 this 添加成員,也就是為對象添加成員. 

  5、 最後函數結束,返回 this,將 this 交給左邊的變量.

 

  分析過構造函數的執行以後,可以得到,構造函數中的 this 就是當前對象.

 

3.2 構造器中的 return

  在構造函數中 return 的意義發生瞭變化,首先如果在構造函數中,如果返回的是一個對 象,那麼就保留原意. 如果返回的是非對象,比如數字、佈爾和字符串,那麼就返回 this,如

果沒有 return 語句,那麼也返回 this. 看下面代碼:

 

復制代碼

 1 // 返回一個對象的 return

 2 var ctr = function() {

 3     this.name = "趙曉虎";

 4     return {

 5         name:"牛亮亮"

 6     };

 7 };

 8 // 創建對象

 9 var p = new ctr();

10 // 訪問name屬性

11 alert(p.name);

復制代碼

執行代碼,這裡打印的結果是"牛亮亮". 因為構造方法中返回的是一個對象,那麼保留 return

的意義,返回內容為 return 後面的對象. 再看下面代碼:

 

復制代碼

 1 // 定義返回非對象數據的構造器

 2 var ctr = function() {

 3     this.name = "趙曉虎";

 4     return "牛亮亮";

 5 };

 6 // 創建對象

 7 var p = new ctr();

 8 // 使用

 9 alert(p);

10 alert(p.name);

復制代碼

代碼運行結果是,先彈窗打印[object Object],然後打印"趙曉虎". 因為這裡 return 的是一個

字符串,屬於基本類型,那麼這裡的 return 語句無效,返回的是 this 對象. 因此第一個打印的 是[object Object]而第二個不會打印 undefined.

 

 

 

四、apply調用模式

  除瞭上述三種調用模式以外,函數作為對象還有 apply 方法與 call 方法可以使用,這便是 第四種調用模式,我稱其為 apply 模式. 

  首先介紹 apply 模式,首先這裡 apply 模式既可以像函數一樣使用,也可以像方法一樣使用 可以說是一個靈活的使用方法. 首先看看語法: 

  函數名.apply(對象, 參數數組);

 

這裡看語法比較晦澀,還是使用案例來說明:

 

  1、 新建兩個 js 文件,分別為"js1.js"與"js2.js";

  2、 添加代碼

 

復制代碼

 1 // js1.js 文件中

 2 var func1 = function() {

 3     this.name = "傳智播客";

 4 };

 5 func1.apply(null);

 6 alert(name);

 7 

 8 // js2.js 文件

 9 var func2 = function() {

10     this.name = "傳值播客";

11 };

12 var o = {};

13 func2.apply(o);

14 alert(o.name);

復制代碼

  3、 分別運行著兩段代碼,可以發現第一個文件中的 name 屬性已經加載到全局對象 window 中;

    而第二個文件中的 name 屬性是在傳入的對象 o 中. 即第一個相當於函數調用,第二個相當

    於方法調用.

 

   這裡的參數是方法本身所帶的參數,但是需要用數組的形式存儲在. 比如代碼:

 

1 // 一個數組的例子

2 var arr1 = [1,2,3,[4,5],[6,7,8]];

3 // 將其展開

4 var arr2 = arr1.conact.apply([], arr1);

  然後介紹一下 call 模式. call 模式與 apply 模式最大的不同在於 call 中的參數不用數組. 看下面代碼就清楚瞭:

 

復制代碼

 1 // 定義方法

 2 var func = function(name, age, sex) {

 3     this.name = name;

 4     this.age = age;

 5     this.sex = sex;

 6 };

 7 // 創建對象

 8 var o = {};

 9 // 給對象添加成員

10 // apply 模式

11 var p1 = func.apply(o, ["趙曉虎", 19, "男"]);

12 // call 模式

13 var p2 = func.call(o, "趙曉虎", 19, "男");

復制代碼

上面的代碼,apply 模式與 call 模式的結果是一樣的.

 

  實際上,使用 apply 模式和 call 模式,可以任意的操作控制 this 的意義,在函數 js 的設 計模式中使用廣泛. 簡單小結一下,js 中的函數調用有四種模式,分別是:函數式、方法式、構造

器式和 apply 式. 而這些模式中,this 的含義分別為:在函數中 this 是全局對象 window,在方

法中 this 指當前對象,在構造函數中 this 是被創建的對象,在 apply 模式中 this 可以隨意的

指定. 在 apply 模式中如果使用 null,就是函數模式,如果使用對象,就是方法模式.

 

 

 

五、案例   

  下面通過一個案例結束本篇吧. 案例說明:有一個p,id為dv,鼠標移到上面去高度增大2倍,鼠標離開恢復. 下面直接上js代碼

 

復制代碼

 1 var dv = document.getElementById("dv");

 2 var height = parseInt(dv.style.height || dv.offsetHeight);

 3 var intervalId;

 4 dv.onmouseover = function() {

 5     // 停止已經在執行的動畫

 6     clearInterval(intervalId);

 7     // 得到目標高度

 8     var toHeight = height * 2;

 9     // 獲得當前對象

10     var that = this;

11     // 開器計時器,緩慢變化

12     intervalId = setInterval(function() {

13         // 得到現在的高度

14         var height = parseInt(dv.style.height || dv.offsetHeight);

15         // 記錄每次需要變化的步長

16         var h = Math.ceil(Math.abs(height – toHeight) / 10);

17         // 判斷變化,如果步長為0就停止計時器

18         if( h > 0 ) {

19             // 這裡為什麼要用that呢?思考一下吧

20             that.style.height = (height + h) + "px";

21         } else {

22             clearInterval(intervalId);

23         }

24     }, 20);

25 };

26 dv.onmouseout = function() {

27     // 原理和之前一樣

28     clearInterval(intervalId);

29     var toHeight = height;

30     var that = this;

31     intervalId = setInterval(function() {

32         var height = parseInt(dv.style.height || dv.offsetHeight);

33         var h = Math.ceil(Math.abs(height – toHeight) / 10);

34         if( h > 0 ) {

35             that.style.height = (height – h) + "px";

36         } else {

37             clearInterval(intervalId);

38         }

39     }, 20);

40 };

發佈留言