iT邦幫忙

2021 iThome 鐵人賽

DAY 13
1
Modern Web

舌尖上的JS系列 第 13

D13 - 做出雞蛋糕 new + Constructor

前言

距離上次的你不知道 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 是巧克力口味。

印出 rabbit1rabbit2 看看成功了嗎?

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
系列文
舌尖上的JS30

1 則留言

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

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

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

我要留言

立即登入留言