iT邦幫忙

2021 iThome 鐵人賽

DAY 13
1
Modern Web

舌尖上的JS系列 第 13 篇

D13 - 做出雞蛋糕 new + Constructor

  • 分享至 

  • xImage
  •  

前言

距離上次的你不知道 Combo 有段時間了,這次要端出的是營養更 up up 的滿漢全席原型系列,包括原型、繼承、constructor、class 等,今天先從 constructor 講起

本文可學到:

  • constructor 是什麼?
  • constructor function
  • new 關鍵字創建過程
  • built-in constructor

Constructor

Constructor 中文譯為「 建構子 」或者是 「 建構式 」,從名稱可以理解應該是要 「建構」也就是創造出什麼,沒錯,在 JS 內可以透過 constructor 製造出結構類似的物件,用雞蛋糕比喻的話,constructor 就是模具,可以依照不同的模具製造出大量相同形狀的雞蛋糕。

從倒入麵糊加熱完成到產生一個雞蛋糕,正式一點的名稱會說,實體化一個物件,產生出來的物件就稱為該建構子的實例 instance。

JavaScript 有內建好的雞蛋糕模具,但也可以設計自己想要的模具,這種模具就稱為 建構式函式 constructor function,加上關鍵字 new 就可以來做雞蛋糕囉!

DIY 雞蛋糕模 new + constructor function

既然叫 constructor function,模具就是透過 function 做出來的,直接實作一個看看

// 做一個名為 RabbitCake 的模具
function RabbitCake(flavor) {
    this.producer = 'Hooo';
    this.shape = 'rabbit';
    this.flavor = flavor;
}

let rabbit1 = new RabbitCake('cream');
let rabbit2 = new RabbitCake('chocolate');

定義一個名為 RabbitCake 的模具,希望做出來的每個雞蛋糕都印有三個資訊,製作者: Hoo、形狀: rabbit、口味:個別指定。
模具好了就可以來做雞蛋糕囉,依照食譜說的要用 new + 模具名稱,參數放想要的口味,第一個 rabbit1 是奶油口味,第二個 rabbit2 是巧克力口味。

印出 rabbit1 和 rabbit2 看看成功了嗎?

console.log(rabbit1)  // RabbitCake {producer: "Hooo", shape: "rabbit", flavor: "cream"}
console.log(rabbit2)  // RabbitCake {producer: "Hooo", shape: "rabbit", flavor: "chocolate"}

成功! 兩個雞蛋糕內都有我們需要的資訊
input 到 output 中間的過程發生了什麼事,繼續往下深究吧

new 出新物件

仔細看程式碼,發現其實 RabbitCake 就是個普通的 function,沒有回傳值出去,照理來說賦值到變數上應該印出 undefined 呀

let rabbit3 = RabbitCake('cream')
console.log(rabbit3)  // undefined

但上面印出的 rabbit1 是個含有三個屬性的物件,中間花生什麼事?

其實是關鍵字 new 發揮作用啦

  1. 使用 new 時, 會先有一個空物件被建立
  2. 呼叫了 RabbitCake 開始執行,呼叫函式建立的 this 函式背景空間會指向了這個新創的空物件,所以在 RabbitCake 只要是以 this. 開頭的都會成為該物件內的屬性
  3. 於是乎,新物件建立了三個指定 this 要有的屬性 producer、shape、flavor
  4. 當 RabbitCake function 執行完畢後,將此已建好屬性的物件回傳並賦值給 rabbit1
  5. rabbi1 印出的屬性就是根據 RabbitCake 中設定的程式碼

在 RabbitCake 內加上 console.log(this) 來看看新物件屬性一個個創建的過程

function RabbitCake(flavor) {
    console.log(this)       // 一開始被創建的空物件
    this.producer = 'Hooo';
    console.log(this)       // 加入第一個屬性 producer
    this.shape = 'rabbit';
    console.log(this)      // 加入第二個屬性 shape
    this.flavor = flavor;
    console.log(this)     // 加上最後一個屬性 flavor,值來自參數
}

let rabbit1 = new RabbitCake('cream');

Constructor function 建構式函式

整理一下使用建構式函式的重點

  • 建構式函式就是個一般的 function ,只因這個函式的目的是搭配 new 關鍵字來創造物件,因此給了特別的名稱
  • 通常建構式函式以大寫開頭來區分,也可以小寫啦,但這樣不好一眼看出
  • 建構式函式若 return 物件,便覆蓋掉上述定義的 this 屬性,因此定義建構式函式時不使用 return
function RabbitCake(flavor) {
    this.producer = 'Hooo';
    this.shape = 'rabbit';
    this.flavor = flavor;
    return {};
}

let rabbit1 = new RabbitCake('cream');
console.log(rabbit1)  // {}

內建模具 built-in Constructor

前面提到 JavaScript 有做好的雞蛋糕模具,他們在哪呢?存在全域物件下

打開瀏覽器的開發者工具,輸入 window 可以看到全域物件的所有屬性,根據 ECMA 19.3 中列出來的 constructor 有 40 種!

可以發現這些 constructor 都是 function 以大寫開頭,昨天學到的 Math 是物件所以不是 constructor 唷!

各大型別也算在內,加上 new 關鍵字後可以創造物件,而本身是 function 也可以呼叫

// 作為 function 呼叫
String()     // string
Number()     // number
Boolean()    // boolean
Date()       // string
Array()      // object
Object()     // object
Function()   // function 

// 加上new 作為constructor呼叫
new String()    // object
new Number()    // object
new Boolean     // object
new Date()      // object
new Array()     // object
new Object()    // object
new Function()  // function

其實不建議對基本型別 constructor 使用 new 關鍵字,因為創出來的都是物件型別,如果能用字面值 literal 表達,就無需動用到 constructor 啦!

ECMA 針對 Array()、Funcion() 描述

在查找 ECMA 時,對於其中描述 Array()、Function() constructor 這兩段很困惑

also creates and initializes a new Array when called as a function rather than as a constructor. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.

creates and initializes a new function object when called as a function rather than as a constructor. Thus the function call Function(…) is equivalent to the object creation expression new Function(…) with the same arguments. - 20.2.1 The Function Constructor

原文意思是當 Array 作為一個 function 而不是 constructor 呼叫時,會建立並初始化一個新的陣列,因此當帶入的參數相同時,使用 Array() 呼叫和作為 constructor呼叫 new Array() 是一樣結果。 (Function 翻譯亦同)

來實驗看看

// 呼叫 Array as a function
let arr = Array(3).fill('Hi')
console.log(arr)    // ['Hi', 'Hi', 'Hi']

// 呼叫 Array as a constructor
let brr = new Array(3).fill('Hi')
console.log(brr)    // ['Hi', 'Hi', 'Hi']


// 呼叫 Function as a function
let afun = Function('a', 'b', 'return a+b')
console.log(afun(2,3))  // 5;
console.log(afun)       // function(a, b) { return a+b}

// 呼叫 Function as a constructor
let bfun = new Function('a', 'b', 'return a+b')
console.log(bfun(2,3))  // 5
console.log(bfun)       // function(a, b) { return a+b}

真的欸,使用 Array()、Function() 都是建立出一個新的陣列或函式,與加上 new關鍵字產生的結果是相同的!

結語

瞭解建構子如何創建物件後,下一章來講講 this 到底是誰

Reference

ECMA
W3Schools
Constructor, operator "new"
建構物件範本:Constructor Function
[筆記] 談談 JavaScript 中內建的 function constructors 及應注意的地方


上一篇
D12 - 那些年算不出來的 Math
下一篇
D14 - 服務生!我要 this this this
系列文
舌尖上的JS 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
Chiahsuan
iT邦新手 4 級 ‧ 2021-09-28 15:57:21

建構式函式若 return 物件,便覆蓋掉上述定義的 this 屬性,因此定義建構式函式時不使用 return

這個很棒~~常常會忘記這個特性
雞蛋糕模具的比喻讚!/images/emoticon/emoticon12.gif

我要留言

立即登入留言