1. 程式人生 > 實用技巧 >函式呼叫_通過建構函式呼叫

函式呼叫_通過建構函式呼叫

<!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>


    /*建構函式建立一個物件,並在該物件也就是函式上下文上新增一個屬性skulk。
    這個skulk方法再次返回函式上下文,從而
    能讓我們在函式外部檢測函式上下文*/
    function Ninja() {

        this.skulk = function () {
            return this;
        }

    }

    var ninja1 = new Ninja();
    var ninja2 = new Ninja();


    //檢測已建立物件中的skulk方法。每個方法都應該返回滋生已建立的物件。
    assert(ninja1.skulk() === ninja1,"The 1st ninja is skulking");
    assert(ninja2.skulk() === ninja2,"The 2nd ninja is skulking");

</script>
</html>
    

這個例子中,我們建立了一個名為Ninja的函式作為建構函式,當通過new關鍵字呼叫時會建立一個空物件例項,並將其作為函式上下文(this引數)傳遞給引數。建構函式中在該物件上建立了一個名為shulk的屬性並賦值為一個函式,是的該函式成為新建立物件的一個方法。
一般來講,會呼叫建構函式時會發生一系列特殊的操作,如下圖所示:使用構建字new呼叫函式會觸發以下幾個動作。

  • 1.建立一個新的空物件。
  • 2.該物件作為this引數傳遞給建構函式,從而成為建構函式的函式上下文。
  • 3.新構造的物件作為new運算子的返回值。

    通過兩次呼叫定義的建構函式,我們建立了兩個新的Ninja物件。值得注意的是,呼叫的返回結果儲存在變數中,後續通過這些變數應用新建立的Ninja物件。
      var ninja1 = new Ninja();
      var ninja2 = new Ninja();

然後執行測試程式碼,確保沒吃呼叫該方法都對預期的物件進行操作:

      assert(ninja1.skulk()===ninja1,"The 1st ninja is skulking");
      assert(ninja2.skulk() ===ninja2,"The 2nd ninja is skulking");


結果完全正確!現在知道通過建構函式建立和初始化一個新的物件了。通過關鍵字new 呼叫函式將返回新建立的物件。但接下來確認是否始終如此。

建構函式返回值

建構函式的目的是初始化新建立的物件,並且新構造的物件會作為建構函式的呼叫結果(通過new運算子)返回。但當建構函式自身有返回值時會是什麼結果?通過下面例子探討這種情況。

<!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>

    //定義一個叫做Ninja的建構函式
    function Ninja() {

        this.skulk = function() {
            return true;
        }

        //建構函式返回一個確認的原始型別值,即數字1
        return 1;

    }

    //該函式以函式的形式被呼叫,正如預期,期返回值為數字1
    assert(Ninja() ===1,"Return value honored when not called as a constructor");

    //該函式通過new 關鍵字以建構函式的形式被呼叫
    var ninja = new Ninja();

    //通過測試表明,返回值1被忽略了,一個新的被初始化物件被通過new關鍵字所返回。
    assert(typeof ninja === "object","Object returned when called as a constructor");
    assert(typeof ninja.skulk==="function","ninja object has a skulk method");

</script>
</html>  

如果執行這段程式碼,會發現一切正常。事實上,這個Ninja函式雖然返回簡單的數字1,但對程式碼的行為沒有顯著的影響。如果將Ninja作為一個函式呼叫,的確會返回1,但如果通過new 關鍵字將其建構函式呼叫,會構造並返回一個新的ninja物件。截至目前,一切正常。

但如果嘗試做一些改變,一個建構函式返回另一個物件。

<!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>

    //建立一個全域性物件,該物件的rules屬性設定為false
    var puppet = {
        rules:false
    }

    //儘管初始化了傳入的this物件,返回該全域性物件。
    function Emperor() {
        this.rules = true;
        return puppet;
    }

    //作為建構函式呼叫該函式
    var emperor = new Emperor();

    //測試表明,變數emperor的值為由建構函式返回的物件,而不是new表示式所返回的物件。
    assert(emperor===puppet,"The emperoris merely a puppet!");
    assert(emperor.rules === false,"The puppet does not know how how to rule!");

</script>
</html>   

這個示例中採用的方式略有不同。首先建立了一個全域性物件,通過puppet引用它,並將其包含的rules屬性設定為false:

      var puppet = {
            rules:false;
      }

然後定義了一個Emperor函式,她會為新構造的物件新增一個rules屬性並設定為true。注意,Emperor函式還有一個特殊點,它返回了全域性的puppet物件:

      function Emperor() {
            this.rules = true;
            return puppet;
      }

之後通過關鍵字new將Emperor作為建構函式呼叫:

      var emperor = new Empoeror();

這裡設定了一種模稜兩可 的情況,新生成的物件會傳遞給建構函式作為函式上下文this,同時被初始化。但我們顯示的返回一個完全不同的puppet物件時,哪個物件最終作為建構函式的返回值呢?

      assert(emperor===puppet,"The emperoris merely a puppet!");
      assert(emperor.rules === false,"The puppet does not know how how to rule!");


測試結果表明,puppet物件最終成為建構函式呼叫的返回值,而且在建構函式中對函式上下文的操作都是無效的。最終都會返回puppet。
測試總結:

  • 如果建構函式返回一個物件,則該物件將作為整個表示式的值返回,而傳入建構函式的this將被丟棄。
  • 但是,如果建構函式發揮的是非物件型別,則忽略返回值,返回新建立的物件。
    正是由於這些特性,構造含糊的寫法一般不用於其他函式。接下來進行更詳細的探討。

編寫建構函式的注意事項

建構函式的目的是根據初始條件對函式呼叫建立的新物件進行初始化。雖然這些函式也可以被"正常"使用,或者被賦值的物件屬性從而作為方法呼叫,但這樣並沒有太大的意義。例如:

      function Ninja() {
            this.skulk = function() {
                  return this;
            }
      }
      var whatever = Ninja();

我們可以將Ninja作為一個簡單函式呼叫,如果在非嚴格模式下呼叫的話,skulk屬性將建立在window物件上=== 這並非一個十分有效的操作。嚴格模式下情況會更糟,因為在嚴格模式下this為定義,因此javascript應用將會崩潰。但
這是好事情,如果在非嚴格模式下犯這樣的錯誤,很可能被忽略(除非有很好的測試),但在嚴格模式下則暴露無疑。這也是推薦使用嚴格模式的一個很好示例。
因為建構函式通常以不同普通函式的方式編碼和使用,並且只有作為建構函式呼叫時才有意義。因此出現了命名約定來區分建構函式和普通的函式及方法。
函式和方法命名通常一描述其行為(skulk,creep,sneak,doSomethingWonderful等)的動詞開頭,且第一個字母小寫。而建構函式則通常以描述所構造物件的名詞命名,並以大寫字母開頭:Ninja,Samurai,Emperor,Ronin等。
很顯然,通過建構函式我們可以更優雅的建立多個遵循相同模式的物件,而無需一次次重複相同的程式碼。通用程式碼只需要作為建構函式的主體寫一次即可。