iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
自我挑戰組

重新複習JavaScript系列 第 27

[Day -27] Function constructor(函式建構式)

  • 分享至 

  • xImage
  •  

昨天有提到Function constructor但並沒有深入講解,今天就來補齊這個部分。

建立object的方式在之前有說過兩個方式:

1.使用 object literal,也就是大括號的方式來建立物件。

2.使用 new Object( )的方式。

但除了上述兩個方式之外,我們還可以使用function constructor(函式建構式)的方式來建立物件。

Function Constructor

昨天有看過這個案例吧:

function Person (){
  this.firstName = 'John';
  this.lastName = 'Doe';
}

var john = new Person();
console.log(john);

它的輸出:

透過 new 它會幫我們建立一個物件,然後在Person這個function裡面有了屬性名稱跟屬性值。

還記得之前有看過這張表嗎?

new其實是運算子(operators)的其中一種,所以當我們使用 new 這個運算子時,會先有一個空的物件被建立。

我們知道,當function 被調用時,在execution context 中會有 this 被建立,而當我們使用 new 的時候,function 裡面的 this 會被指定成剛剛所建立的那個空物件。

所以當Person這個函式被invoke(調用)時,是在幫這個空物件賦予屬性名稱和屬性值。

我們看一下2個範例,來更了解這個執行的過程。

範例1:

function Person() {
    console.log(this);
    this.firstName = "John";
    this.lastName = "Doe";
    console.log('This function is invoked')
}

var john = new Person();
console.log(john);

在這個範例我們可以確認兩件事情:

  1. new 會幫我們建立一個空的物件。
  2. 這個function有確實被invoke。

我們看下一個範例。

範例2:

function Person() {
    console.log(this);
    this.firstName = "John";
    this.lastName = "Doe";
    console.log('This function is invoked')
    return {greeting: 'i got in the way'}
}

var john = new Person();
console.log(john);

我讓這個function 回傳一個object ,最後執行結果不再是範例1的結果,而是會改成我們新建立的物件。

因此我們可以說,只要這個函式建構式 Person 沒有指定 return 其他的物件,它就會直接回傳給我們設置this的變數。

如果我們這時候再新增一個object jane:

function Person() {
    console.log(this);
    this.firstName = "John";
    this.lastName = "Doe";
    console.log('This function is invoked')
}

var john = new Person();
console.log(john);
var jane = new Person();
console.log(jane);

我們會看到它們都指向同一個function,因此我們把code稍微做個修改:

function Person(firstname, lastname) {
 
    console.log(this);
    this.firstname = firstname;
    this.lastname = lastname;
    console.log('This function is invoked.');
    
}

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

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

只要我們讓物件的屬性值變成參數,就能透過function constructor 建立出許多同屬性名稱但不同屬性值的物件。

所以要記得:

1.函式建構式(function constructor)就是普通的 function,只是我們可以透過這個 function 來建立物件。

2.透過在 function 前面加上 new 這個運算子,它會把函式中 this 這個關鍵字建立成一個新的物件,然後如果你沒有在該函式的最後指定回傳出其它物件的話,它就會自動回傳這個新的物件給你。

補充:如果在撰寫程式碼時,忘記加上 new 的話:

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

!https://i.imgur.com/ygaiR0t.png

這時候的this就會指向window,因此我們可以看到firstnamelastname出現在裡面。

至於為什麼會回傳undefined則是因為如果沒有說function要return什麼的話,預設就是會return undefined。

設定物件原型

剛剛我們提到了怎麼用 Function Constructor搭配關鍵字 new來建立物件,現在來學用 Function Constructor來設定原型。

之前有提到,在 JavaScript 中的 function其實也是一種物件,其中包含一些屬性像是該函式的名稱(Name)和該函式的內容(Code),但其實 function 這裡面還有一個屬性,這個屬性稱做 prototype,這個屬性會以空物件的型式呈現。

JavaScript: Understanding the Weird Parts的課堂上有一段話:

💡 And all functions, every function, every function in JavaScript you've ever written in your life, has a prototype property that starts off its life as an empty object, and unless you're using the function as a function constructor, it just hangs out

簡單來說就是函數有一个原型属性,默認為空的物件,除非它用作function constructor,否則它就只是待在那里。(chatGPT AI翻譯)

這邊就出現一個很困惑的一段話:

💡 The prototype property on a function is not the prototype of the function. It's the prototype of any objects created if you're using the function as a function constructor.

函式當中 prototype 這個屬性並不是這個函式的 prototype,它指的是所有透過這個 function constructor 所建立出來的物件的 prototype。

我們透過下方的code來了解一下這句話的意思

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);

如上圖所示,我們打上Person.prototype後,出現了{constructor: ƒ}

,表示 Person函式有一個原型物件,其中包含一個名稱為 “constructor” 的函式。

而這個 “constructor” 函式就是我們所定義的 Person函式,它是一個function constructor,用於創建物件的實例。

因此,當我執行 var john = new Person("John", "Doe");和 var jane = new Person("Jane", "Doe");時,實際上是創建了兩個繼承自 Person.prototype的物件實例,它們共享了一個function constructor和一些公共屬性。

之後我們再加入一段code:

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

Person.prototype.getFullName = function() {
  return this.firstName + ' ' + this.lastName;
}

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

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

我們可以看到Person.prototype多出了一個名為getFullName的函式,而且在Person函式中也出現了一個getFullName,還記得剛剛有提到:

函式當中 prototype 這個屬性並不是這個函式的 prototype,它指的是所有透過這個 function constructor 所建立出來的物件的 prototype。”

因此,透過這個方式,我們就可以完成以下的code,讓我們在最後新增1行code:

console.log(john.getFullName());

我們就可以得到:

Prototype 的實用處

至於這樣做有什麼好處呢?我們可以想想,當我今天有 1000 個物件是根據這個function constructor所建立,而今天我有一個method(方法)想要讓大家使用,我就可以用這個方式。

但為什麼我不能直接加在函式裡面就好,還要透過這個方式呢?

這就要牽扯到效能的問題了,我們當然可以把它直接放在函式裡,但不要忘了:

Functions in JavaScript are objects

他們是會佔據記憶體空間的,因此如果我有1000個物件,就表示我會因為這個原因而需要1000個空間來放這個method(方法),但如果我今天是使用是建立在 prototype 中,我們只需要一個空間來存放method(方法)。

所以,為了效能上的考量,通常會把method(方法)放在建構式的 prototype 中,因為它們可以是通用的;把property(屬性)放在建構式當中,因為每一個物件可能都會有不同的屬性內容,如此將能有效減少記憶體的問題。

Prototypal Inheritance(原型繼承)

最後,我們來談談prototypal inheritance,還記得我們之說過繼承可以分成兩種,一種是 classical inheritance,這種方式用在 C# 或 JAVA 當中;另一種則是 JavaScript 所使用的,是屬於 prototypal inheritance。

這邊會介紹一個大部分瀏覽器都支援的語法:Object.create()

看一下下面這個例子:

var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

注意這邊用的是this.firstName,他會指向Person這個object,但如果沒用this的話,他則是會在getFullName 這個 execution context 中去找我們需要的變數,找不到就會到最外層的全域環境去找。

這時候我們加入Object.create():

var john = Object.create(Person);
console.log(john);

我們可以從上面這張圖知道,john會是一個空物件,但是它繼承了 Person 這個物件當中的屬性和方法。

接下來我們把code修改一下:

var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

var john = Object.create(Person);

john.firstName = 'John';
john.lastName = 'Doe';
console.log(john);
console.log(john.getFullName());

輸出結果為:

之所以會這樣,是因為prototype chain(原型鍊)。

我們先從圖片的第一行來說,因為 firstName 和 lastName 在該物件已經有這兩個屬性

john.firstName = 'John';
john.lastName = 'Doe';

因此它不會在往該物件的原型去尋找。

而對 getFullName 來說,因為在 john 這個物件裡沒有這個方法,於是就會到 prototype 裡面去找,最後會回傳 “John Doe”。

透過 Object.create() 這種方法,是最單純使用 prototypal inheritance 的方式。如果你想要一個物件的原型,就先建立一個 A 物件當做其他物件的基礎,然後再建立另一個空物件 B,指稱 A 物件當做它的原型,在透過為 B 物件賦予屬性或方法。


呼結束了~為了這兩篇又去重新看了一次JavaScript: Understanding the Weird Parts ,並且參考了很多前輩觀看這課程的心得而得出這兩篇文章,目前我的知識含量還無法很明白地說出這些東西,希望透過這次的整理讓我更加理解。

到此本次鐵人賽原本預定的章節已經全部複習完畢,剩下兩天會針對AJAX來做一次學習。


上一篇
[Day -26] 原型、繼承和原型鍊
下一篇
[Day -28] AJAX與溝通協定
系列文
重新複習JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言