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 及应注意的地方


<<:  Day 14 -资料查询语言 LIKE !

>>:  【Day 13】Google Apps Script - API 篇 - Drive Service - 云端硬碟服务范例

LeetCode解题 Day18

282. Expression Add Operators https://leetcode.com...

Day29 订单 -- 订阅

其实订阅订单跟定期定额作法非常相似, 这边会额外拉出来讲是因为个人对於他们两兄弟有不同定义, 文章是...

【设计+切版30天实作】|Day6 - 设计出背景上又有背景的吸睛小广告

设计大纲 上一个区块设计了使用者的「痛点」,因此接下来的区块想要做一个「平台的小广告」,让使用者了解...

从单元测试探讨 MVC to MVVM 的差异

从单元测试探讨 MVC to MVVM 的差异 你在这里学到什麽? 用 RxSwift DataBi...

那些被忽略但很好用的 Web API / 前言

Web API -- Application Programming Interface for ...