構造器函數(Constructor functions)的定義和任何其它函數一樣,我們可以使用函數聲明、函數表達式或者函數構造器(見以前的隨筆)等方式來構造函數對象。函數構造器和其它函數的區別在與它們的調用方式不同。
要以構造函數的方式調用函數,只需要在調用時在函數名稱前加new 關鍵字,比如:function whatsMyContext(){ return this; }; 調用:new whatsMyContext();
以構造函數的方式調用函數是JavaScript中非常強大的特性,下面來探究它的神秘之處:
function Ninja() { this.skulk = function() { return this; }; } 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");
上面的demo中,首先定義了一個構造器函數,這個構造器函數會在函數的上下文對象上創建一個名為shulk的函數屬性。接着通過以構造函數的方式調用函數Ninja來創建兩個Ninja(類型)對象,並且分別被ninja1和ninja2引用,最后通過自定義函數assert來驗證每個對象的skulk方法,每個shulk方法應該返回每次以new的方式調用Ninja時構造出來的函數對象。
下面來看一下這個過程中發生了什么:
1.一個全新的空的對象被創建;
2.這個空的對象被傳遞到構造器函數並且作為它的this參數,因此成為了這個構造器函數的上下文對象;
3.在這個新的空的對象上添加屬性,shulk
4.這個被構造出來的對象被作為函數的返回值返回。
使用構造器函數的目的是為了創建新的對象,對這個對象進行操作並且這個對象作為構造器函數的返回值返回。任何和這個目的無關的函數都不適合作為構造器函數。在函數沒有返回值的時候以構造函數的方式調用這個函數會返回新創建的上下文對象,那么在函數返回一個值(基本類型)的時候會有什么變化嗎?看下面的例子:
function Ninja() { this.skulk = function () { return true; }; return 1 } assert(Ninja() === 1,"Return value honored when not called as a constructor"); var ninja = new Ninja(); assert(typeof ninja === "object","Object returned when called as a constructor"); assert(typeof ninja.skulk === "function","ninja object has a skulk method");
運行上面的例子,所有的斷言都可通過,事實上Ninja函數返回一個簡單類型的值1對代碼的執行沒有產生任何影響:如果以函數的方式調用Ninja它將返回1,而以構造函數的方式調用它,會返回new產生的函數上下文對象。 如果函數返回另一個對象會發生什么?
var puppet = { rules: false }; function Emperor() { this.rules = true; return puppet; } var emperor = new Emperor(); assert(emperor === puppet, "The emperor is merely a puppet!"); assert(emperor.rules === false, "The puppet does not know how to rule!");
這次和上面的例子不同,首先創建一個puppet對象並把它的rules屬性設置為false;接着定義一個名為Emperor的函數,並且把它的上下文變量的rules屬性設置為true,但是函數最后卻把全局的puppet對象作為返回值返回。再接着以構造函數的方式調用Emperor函數;這樣產生一個有歧義的情況,我們獲得了一個作為函數上下文的對象(this),但是卻返回了一個完全不同的對象,這種情況下哪一個對象會被返回呢?
根據后面assert函數的斷言測試可以看到,被返回的是puppet函數。下面來總結一下:
1.如果構造器函數返回一個對象,這個對象會作為new表達式的返回值返回,在以new方式調用函數時產生的作為函數上下文(this)的對象會被丟棄;
2.如果構造器函數沒有返回對象(返回值為值類型),這個返回的值會被丟棄,以new方式調用函數時產生的作為函數上下文(this)的對象會被返回。
構造器函數的代碼習慣:
構造器函數的目的是為函數調用時產生的上下文對象進行初始化並返回這個對象,雖然這些構造器函數可以以函數或者方法的形式調用,通常情況下那么做並沒有什么意義,比如:
function Ninja() { this.skulk = function() { return this; }; } var whatever = Ninja();
簡單的以函數方式調用Ninja,skulk會被綁定到全局變量window上(非嚴格模式下),這不是特別有用的操作,在嚴格模式下更沒有意義,因為上下文對象this為undefined,因此會拋出不能為undefined設置屬性的異常。在非嚴格模式下,這樣做很容易出現莫名其妙的狀況,這很難被察覺到因為它沒有拋出任何異常。
因為構造器函數也是普通的函數,只是使用不同的方式調用而已,通常情況下,對於構造器函數的定義有不同的命名習慣:
以動詞開頭用以描述它是來做什么的並且首字母小寫的函數名適合普通的函數或者對象的方法;構造器函數的命名通常是一個名詞,用來描述要創建的對象,首字母通常大寫。