2016年6月22日

[筆記] 談談 JavaScript 中的 function constructor 和關鍵字 new

img
在前幾篇的筆記中,我們已經對於物件(object)、原型(prototype)、繼承(inheritance)和原型鏈等等有更多的了解,現在讓我們來更深入的談論一下JavaScript中建立物件的方法。
先前我們有說明使用 object literal,也就是大括號的方式來建立物件,或者是使用 new Object( ) 的方式,但除此之外,我們還可以使用**函式建構式(function constructor)**的方式來建立物件。但在開始深入了解之前,讓我們先來簡單談一下 JavaScript 發展的歷史,如此我們將更清楚為什麼會使用這樣的方式。

new 和 JavaScript 的關係史

JavaScript 是由一個叫做 Brendon Eich 所開發的,就像現在有 Google VS. Apple,在當時也有很多不同的程式語言之間在競爭,而之所以這個語言會叫做 JavaScript 其中一個原因是當初宣傳這套語言時,想要吸引使用 JAVA 的程式開發者,這就很像微軟發展了 VBScript,之所以稱作 VBScript 也是因為想要吸引使用 Visual Basic 的程式開發者。
JavaScript 雖然聽起來或看起來和 JAVA 很像,但實際上可以說是非常的不同,然而,當初在推廣 JavaScript 這套語言的時候,為了讓它和 JAVA 有更多的相似性,因此用了和 JAVA 相似的語法 var XXX = new ooo( ) ,這樣的方式在 JAVA 中可以根據某個類別(class)來建立物件,但在 JavaScript 中並沒有真正的 class 這樣的東西,這麼做的目的只是為了讓 JAVA 的使用者在看到 JavaScript 的時候覺得有股熟悉感。
在了解這段小歷史之後讓我們進一步來看函式建構式(function constructor)new

談談函式建構式(function constructor)

我們先建立一個函式,名稱叫做 Person,要注意的是我們在第9行的地方是使用 new 這個關鍵字,後面放的則是 function Person:
function Person (){
  this.firstName = 'John';
  this.lastName = 'Doe';
}

var john = new Person();
console.log(john);
接著我們把 John 呼叫出來看,會得到如下的結果,透過 new 它會幫我們建立一個物件,然後裡面有Person 這個 function 裡面的內容,並且變成了屬性名稱和屬性值:
img

進一步來看 new 讓這個過程發生了什麼

在JavaScript中 new 這個關鍵字其實是眾多運算子(operators)的其中一種:
img
圖片來源:MDN Operator Precedence
當我們使用 new 這個關鍵字時,實際上會先有一個空的物件被建立。
接著 People 這個函式會被執行(invoke)。我們知道當函式執行的時候,在 execution context 中會有 this 被建立,而當我們使用 new 的時候,函式裡面的 this 會被指定成剛剛所建立的那個空物件
所以當執行 People 這個 function,執行到 this.firstNamethis.lastName 時,因為 this 現在指稱的是那個空物件,所以實際上是在幫這個空物件賦予屬性名稱和屬性值。
在這樣的過程中,只要這個函式建構式 People 沒有指定 return 為其他物件,它就會直接回傳給我們這個新建立的物件
接著讓我們透過程式碼來更清楚的了解這個執行的過程:

該函式有被執行

讓我們在原本的程式中加入第6行的內容:
function Person () {
  this.firstName = 'John';
  this.lastName = 'Doe';
  console.log('這個函式有被執行');
}

var john = new Person();
console.log(john);
這時候在 chrome 中呼叫出來的結果如下,說明了當我們使用 new 在建構物件的時候 People 這個 function 確實有被執行:
img

透過 new 會幫我們建立一個空的物件

現在我把我們原本的程式碼改成這樣:
function Person(){
  console.log(this);
}

var john = new Person();
//    console.log(john);
這時候程式碼回傳的結果如下,表示的確在執行這個程式的過程中幫我們建立了一個新的空物件
img

函式的最後若return其他物件,則原新物件內容會被覆蓋

現在,讓我們把原本的程式碼稍微做如下修改,增加第六行的內容:
function Person (){
  this.firstName = 'John';
  this.lastName = 'Doe';
  return  {"RETURN":"原本this的內容就不會被回傳"};
}

var john = new Person();
console.log(john);
回傳的結果如下,原本被建立的新物件不會被回傳,而是回傳我們最後return給它的內容:
img

function constructor 的實際應用

由上面的方法,我們可以透過 function 的方式來建立一個新的物件,如果我們想要建立出同屬性名稱但不同屬性值的物件內容,我們可以把物件的屬性值變成參數,如此就能透過此 function constructor 建立出許多不同的物件:
function Person (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

var john = new Person('John', 'Doe');
console.log(john);
var jane = new Person('Jane', 'Doe');
console.log(jane);
如此,我們就可以透過同一個函式建構式建立出很多不同的物件:
img
此外,我們會把根據建構子(constructor)所建立出來的物件稱作是實例(instance)
透過建構子(constructor)所建立出來的物件,我們稱為實例(instance)

注意!如果我們忘了加上關鍵字 new

這裡有一個地方我們需要非常留意,如果你在程式撰寫的過程當中,忘記加上 new 這個關鍵字的話,例如:
var john = Person('John', 'Doe');
console.log(john);
如此,因為 JavaScript 不知道你是要執行這個程式,還是要根據這個 function 去建立object,因次最後回傳 undefined 的結果:
img

最後,讓我們總結一下函式建構式

  • 其實函式建構式(function constructor)就是普通的 function,只是我們可以透過這個 function 來建立物件。
  • 透過在 function 前面加上 new 這個運算子,它會把函式中 this 這個關鍵字建立成一個新的物件,然後如果你沒有在該函式的最後指定回傳出其它物件的話,它就會自動回傳這個新的物件給你。
那....如何透過 function constructors 來設定我們的原型(prototype)呢?讓我們在下一篇筆記來談吧!

程式範例

function Person(firstName, lastName){
  this.firstName = firstName;
  this.lastName = lastName;
}

var john = new Person('John', 'Doe');
console.log(john);

var jane = new Person('Jane', 'Doe');
console.log(jane);

延伸閱讀

資料來源

0 意見:

張貼留言