1. 程式人生 > 實用技巧 >理解函式呼叫_函式呼叫

理解函式呼叫_函式呼叫

我們可以通過4種方式呼叫一個函式,每種方式之間有一些細微的差別。

  • 作為一個函式呼叫(function)--skulk(),直接呼叫。
  • 作為一個方法(method)-- ninja.skulk(),關聯在一個物件上,實現面向物件程式設計。
  • 作為一個函式(constructor)-- new Ninja(),例項化一個新的物件。
  • 通過函式的apply或者call方法-- skulk.apply(ninja)或者skulk.call(ninja)。例如:
      function skulk(name){}
      function Ninja(name){}

      //作為函式呼叫
      skulk("yongja");
      (function(who){return who})('Hattori');


      var ninja = {
            skulk:function(){}
      }
      //作為物件方法呼叫
      ninja.skulk('yongjar');

      //作為建構函式呼叫
      ninja = new Ninja('Yongjar');
      //通過call方法呼叫
      skulk.call(ninja,'Yongjar');
      //通過apply方法呼叫
      skulk.apply(ninja,['Yongjar']);

除了call和apply的方式外,函式呼叫的操作符都是函式表示式加一對圓括號,下面來開始探討--作為函式直接被呼叫。

作為函式直接被呼叫

作為函式呼叫?聽起來是多麼愚蠢,函式當然要被作為函式呼叫,實際上,這我們說的函式"作為一個函式"被呼叫是為了區別其他的呼叫方式:方法、建構函式和apply/call。如果一個函式沒有作為方法,建構函式或者通過apply和call被呼叫的話,我們就成為作為函式被直接呼叫。
通過()運算子呼叫一個函式,且被執行的函式表示式不是作為一個物件的屬性存在時,就屬於這種呼叫型別。(當執行的函式表示式是一個物件屬性時,屬於接下來將要討論的方法呼叫型別)這裡有一些簡單的示例:

      // 函式定義作為函式被呼叫
      function ninja() {}
      ninja();


      // 函式表示式作為函式被呼叫
      var samurai = function(){}
      samurai();
      //會被立即呼叫的函式表示式,作為函式被呼叫。
      (function(){})()

當以這種方式呼叫時,函式上下文(this關鍵字的值)有兩種可能性,在非嚴格模式下,它將是全域性上下文(window物件),而在嚴格模式下,它將是undefined。

<!DOCTYPE html>
<html lang="en">
<head>  
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../unitl/test.js"></script>
    <style>
        #results li.pass {color:green;}
        #results li.fail {color:red;}
    </style>
</head>
<body>
    <ul id="results"></ul>
</body>
<script>



    //非嚴格模式下的函式
    function ninja() {
        return this;
    }

    //嚴格模式下的函式
    function samurai() {

        "use strict";
        return this;

    }

    //非嚴格模式下的函式以window物件作為函式上下文
    assert(ninja() === window,"In a 'nonstrict ninja function the context is the global window object'");


    assert(samurai() === undefined, "In a 'strict' function,the Context is undefined");
    
    

</script>
</html>
    

注意

  很顯然,在多數情況下,嚴格模式比非嚴格模式更簡單易懂,例如,在清本例中使用的函式呼叫的方式(而不是作為方法被呼叫),並沒有指定函式被呼叫的物件。因此在我們看來,this
關鍵字的確應該被設定為undefined(在嚴格模式下),而不應該是全域性的window物件(在非嚴格模式下)。一般而言,嚴格模式修復了很多javaScript中類似的怪異表現(如arguments的用法)。

作為方法被呼叫

  • 當一個函式被給被賦值給一個物件的屬性,並且通過物件屬性應用的方式呼叫函式時,函式會作為物件的方法被呼叫。示例如下:
      var ninja = {};
      ninja.skulk = function(){};
      ninja.skulk();

這種情況下函式被稱為方法,如果你有面向物件程式設計的經歷,一定會聯想到是否可以在方法內部通過this方法訪問物件主題。這種情況下同樣適用。當函式作為某個物件的方法被呼叫時,
該物件成為函式的上下文,並且在函式內部可以通過引數訪問到。這也是javaScript實現面向物件程式設計的主要方式之一。(建構函式是另外一種方式)。
通過下面例子來說明函式作為函式呼叫和作為方法呼叫的異同點。

<!DOCTYPE html>
<html lang="en">
<head>  
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../unitl/test.js"></script>
    <style>
        #results li.pass {color:green;}
        #results li.fail {color:red;}
    </style>
</head>
<body>
    <ul id="results"></ul>
</body>
<script>



    //返回函式上下文,從而讓我們能從函式外面檢查函式上下文
    function whatsMyContext(){

        return this;

    }

    //作為函式被呼叫並將其上下文設定為window物件。
    assert(whatsMyContext() === window,"Function call on window");


    var getMythis = whatsMyContext;

    //使用變數getMythis來呼叫函式,該函式仍然作為函式被呼叫,函式上下文依然是window物件。
    assert(getMythis() === window,"Anthor function call in window");

    //建立一個物件ninja1其屬性getMythis得到了函式whatsMyContext的引用。
    var ninja1 = {
        getMythis:whatsMyContext
    }

    // 使用ninja物件的方法getMythis來呼叫函式。函式的上下文現在是ninja1了。這就是面向物件。
    assert(ninja1.getMythis() === ninja1,"Working with 1st ninja");

    //建立一個物件ninja2其屬性getMythis得到了函式whatsMyContext的引用。
    var ninja2 = {
        getMythis:whatsMyContext
    };

    //使用ninja2物件的方法getMythis來呼叫函式。函式上下文現在是ninja2.
    assert(ninja2.getMythis() === ninja2,"Working with 2nd ninja");


</script>
</html>
    

這段測試程式碼中設定了一個名為whatMyContext的函式,在整個程式中都將用到它。這個函式的唯一功能就是返回它的函式上下文,這樣就可以在函式外部看到呼叫函式的上下文。

      function whatsMyContext() {
            return this;
      }      

當直接通過函式名呼叫,也就是將函式作為函式呼叫時,因為實在非嚴格模式下執行,因此預期的函式上下文結果應當是全域性上下文(window)。斷言如下:

      assert(whatsMyContext() === window,...);

然後通過變數getMythis建立了whatMyContext函式的一個引用,var getMythis = whatsMyContext。這樣就不會重複建立函式的示例。他僅僅是建立了原函式的一個引用(因為函式是第一類物件)。
因為函式呼叫操作符可以應用任何表示函式的表示式,所以可通過變數呼叫函式,這裡再一次將函式作為函式呼叫。我們預期的函式上下文是window,斷言如下:

      assert(getMythis()===window,"Anther function call in window");

現在,假設遇到了一個棘手的問題,需要定義一個名為ninja1的物件,幷包含一個名為getMythis的屬性,屬性值為函式whatMyContext的引用。這樣我們就在物件上建立了一個名為getMythis的方法。
不要認為whatsMyContext成為了ninja1的一個方法,whatsMyContext是一個獨立的函式,它可以有多種呼叫方式:

      var ninja1 = {
            getMyThis:whatsMyContext
      }

正如之前提到的,當通過方法應用呼叫函式時,我們期望的函式上下文是該方法所在的物件,因此斷言如下:

      assert(ninja1.getMythis() === ninja1,"Working with 1st ninja");

注意

  將函式作為方法呼叫對於實現javascript面向物件程式設計至關重要。這樣就可以通過this在任何方法中獲取該方法的"宿主"物件--這也是面向物件程式設計的基本概念。

為了驗證這一點,我們通過建立一個新的物件ninja2來進一步測試,他也包含了一個應用whatsMyContext函式的名為getMythis的屬性。當我們通過ninja2物件呼叫這個函式時,他的上下文物件即為ninja2:

      var ninja2 = {
            getMyThis:whatsMyContext
      };
      assert(ninja2.getMyThis() === ninja2,"Working with 2nd ninja");

即使在整個示例中使用的是相同的函式--whatsMyContext,但通過this返回的函式上下文依然取決於whatsMyContext的呼叫方式。例如,ninja1和ninja2共享了完全相同的函式,但當執行函式時,該函式可以訪問並操作所屬物件的其他方法。因此我們不需要建立一個單獨的函式副本來操作不同的物件進行相同的處理。這也是面向物件程式設計的魅力所在。