# [JavaScript] 閱讀 Constructors Are Bad For JavaScript 後的整理
###### tags: `前端筆記`
## 為什麼要讀這篇?
因為我目前正在學習 [FACTORY FUNCTIONS AND THE MODULE PATTERN](https://www.theodinproject.com/paths/full-stack-javascript/courses/javascript/lessons/factory-functions-and-the-module-pattern#factory-function) 文章的最後有自我檢核,但我不知道該怎麼回答第一題:*Describe common bugs you might run into using constructors.* 點了連結後頁面被導向這篇文章 [Constructors Are Bad For JavaScript ](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/),我覺得讀完大概就可以理解了,所以決定花時間好好消化。
## 先來介紹建構子(constructor)
### 使用時機
如果想要建立很多擁有相通屬性的物件,可以先建立一個建構子(constructor)當作模型的藍圖,之後再搭配 `new` 關鍵字來新增實例物件(instance object)。因為實例物件(instance object)是以建構子為藍圖製造,所以就會有該藍圖的屬性(property)。
```javascript=
// 建構子
function Person (name, age, hasPet) {
this.name = name;
this.age = age;
this.hasPet = hasPet;
}
const lun = new Person ('lun', 23, false);
// Person{name: 'lun', age: 23, hasPet: false}
```
### 如果有方法呢?要在建構子裡面寫嗎?
可以,但是這樣子會讓每個實例物件(instance object)都自己擁有自己的方法,這樣子只是一直重複而已,沒有「繼承」。
```javascript=
function Person (name, age, hasPet) {
this.name = name;
this.age = age;
this.hasPet = hasPet;
this.sayHi = function () {
console.log(`${name} says Hi!`);
}
}
const lun = new Person('lun', 23, false);
const meimei = new Person('meimei', 25, false);
```
兩個物件擁有「自己」的 `sayHi` 方法(method)

==另一種活用「繼承」的方式==
```javascript=
function Person (name, age, hasPet) {
this.name = name;
this.age = age;
this.hasPet = hasPet;
}
Person.prototype.sayHi = function() { console.log(`${this.name} says Hi`); }
const lun = new Person('lun', 23, false);
```
方法「被繼承」了,而不是物件本身擁有。

>所以方法不要寫在建構子(constructor)裡,因為當物件多的時候就會造成資源浪費。
==推薦使用 `Object.create()` 這樣子就不用寫藍圖,直接繼承一個物件==
```javascript=
const Person = {
name: null,
age: null,
hasPet: false,
greeting: function () {
console.log(`${this.name} says Hi!`);
}
}
const lun = Object.create(Person);
```
儘管 `lun` 在一開始的時候本身沒有屬性,但因為「繼承」的關係可以直接使用繼承得到的屬性。之後 `lun` 有自己的屬性也不會影響繼承原本的東西(也就是 `Person`)。

## 回到文章,為什麼建構子(constructor)不好呢?
### 1. 如果忘記用 `new` 很危險
#### 先回來談談使用建構子時,`new` 到底做了蝦米?
*範例參考([ref.](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/))*
```javascript=
function C() {
this.instance_member = "whoops!"
}
const c = new C();
// JavaScript 是這樣子看待上面的代碼的
const c = {};
C.call(c);
```
`new` 讓人類不用自己用 `Function.prototype.call()` 使實例物件(instance object)的 `this` 可以不會回到預設指向 `window` / 在 Node.js 則是 `global`。
但如果不小心忘記加 `new` 會怎麼辦?
```javascript=
function C() {
this.instance_member = "whoops!"
}
const c = C(); // Forgot "new"
c;
window.instance_member; // Property added to global namespace!
```
就會發現==沒有報錯==,而且 ==`this.instance_member` 就回到預設 `this` 指向 `window` / 在 Node.js 則是 `global`==。

### 2. 如果改了建構子(constructor)的 `prototype`,之後依照此建構子(constructor)創建的實例物件(instance object)的 `prototype` 就會出現問題
#### 首先要探討要怎麼 A 是否為 B 的實例物件(instance object)?也就是說要怎麼知道誰繼承誰呢?
>The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value.
>`instanceof` 依建構子內的 `prototype` 屬性為基準,如果在實例物件中找到與建構子相同的 `prototype` 就會回傳 `true` => A 是 B 的實例物件;回傳 `false` 則代表 A 不是 B 的實例物件。
```javascript=
function A () {}
function B () {}
const c = new A();
// c 為 A 的實例物件
c instanceof A;
=> true
// c 並不是繼承 B
c instanceof B;
=> false
```
#### 所以改了建構子的 `prototype` 對實例物件會有什麼差別嗎?
這邊先說明一個概念:
by default 宣告函式時,函式的 `prototype` 就會初始化新增一個叫做 `constructor` 的屬性並指向函式本身。而參照建構子新增的物件實例的 `prototype` 就會有從建構子繼承的 `prototype`。
所以這也就是為什麼要把方法寫在 `prototpye` 中,這樣子實例物件在叫用時,實例物件本身抓不到,但因為可以從 `prototype` 抓,所以最後成功叫用。
所以會到問題本身,到底會出現什麼問題呢?
##### *範例1.[ref.](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/)*
已知實例物件的 `prototype` 是繼承建構子的 `prototype` 如果產生完再更改建構子的 `prototype` 那麼實例物件會有影響嗎?
==答案是要看怎麼改 XDDD。==
What the hell is going??????
回到之前的筆記 [pass by value(傳值)、pass by reference(傳址)還是 pass by sharing](https://hackmd.io/2XXlJQ67RMa9Ekh9nU0NJg?view) 有學習到==基本型別是不能變的,但是物件型別可以==,所以如果是使用.(dot notation)就可以改變實際物件的值,但是如果是用物件實字(object literal)也就用花括號({})則是直接在 `Call Stack` 及 `Heap` 新增新的記憶體,因為對 JavaScript 來說物件實字(object literal)不是修改,是創建一個新的物件。
```javascript=
// Constructor
function C() {}
// Create object.
const c = new C();
c instanceof C;
=> true
c.constructor === C;
=> true
// Change prototype
C.prototype = { prototype_prop : "proto"};
c.constructor === C;
=> true
c instanceof C; // instanceof no longer works!
=> false
```
可以稍微簡單的把記憶體指向圖畫出來

因為 `instanceof` 是以建構子的 `prototype` 為基準在實例物件查找有無與建構子相同的 `prototype`。因為修改 `C.prototype` 是用物件實字(object literal),所以即便肉眼看跟流程都顯示 `c` 確實是 `C` 的實例物件,但是當用 JavaScript 提供的工具 `instanceof` 就會得到 `false`,因為 `c.prototype` 已經和 `C.prototype` 指向不同的物件了。
##### *範例2.[ref.](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/)*
```javascript=
// 原本 C 的 prototype 中會有屬性 constructor 並指向 C 本身
function C() {}
// 但後來把 C 的 prototype 被改寫了
C.prototype = { prototype_prop : "proto"};
const c = new C();
// instanceof 會從建構子找 prototype,並在實例物件檢查有沒有相同的 prototype
// 因為 c 是 C 的實例物件,所以當 C 的 prototype 被改寫,c 的 prototype 繼承了改寫的 prototype
c instanceof C;
=> true
// 所以 c.constructor 就會回去預設值(不是undefined)喔
c.constructor === C;
=> false
```


##### *範例3.[ref.](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/)*
```javascript=
// Create two constructors with the
// same prototype.
const proto = {protoprop: "protoprop"};
function C() { this.cprop = "cprop" };
C.prototype = proto;
function F() { this.fprop = "fprop" };
F.prototype = proto;
const f = new F();
f.protoprop; // Has prototype properties
=> "protoprop"
f.fprop; // Has F properties
=> "fprop"
f.cprop; // Doesn't have C properties
=> undefined
f instanceof C; // Is an instance of C!?!
=> true
```
因為此時 `C.prototype` 跟 `f.prototype` 是指向相同的記憶體,所以依照 `instanceof` 的規則(只要從實例物件中找到建構子的`prototype` 就 `true`)就會回傳 `true`,即使代碼根本沒這樣子寫。

> c instanceof C does not mean that c was created by C or that c has anything to do with C. It basically just means “at this moment, the prototype C will use if it’s invoked as a constructor (even if it’s never actually invoked as a constructor) appears somewhere in the chain of prototypes of c”. Essentially, it’s equivalent to C.prototype.isPrototypeOf( c ), but the latter is far more upfront about what it’s actually doing.
> 更清楚地來說,當 `c instanceof C` 回傳 `true` 並不代表 C 創造了 c,只代表在此刻(也就是寫這段代碼的時間)C.prototype 指向的記憶體剛好和實例物件 c 的不知道哪個環節的 `prototype` 是相同的而已。
### Describe common bugs you might run into using constructors.
1. 如果在創造實例物件的時候忘記 + `new` JavaScript 不會報錯,但是 `this` 會回到預設指向 `window`(在瀏覽器)/`global`(在 Node.js)
- 可以解決,但超麻煩!
```javascript=
function C () {
// 如果叫用 this 的物件執行 instanceof C 為 false
// 該物件的 prototype 找不到和指向建構子的屬性
// => 重新 return
if (!(this instanceof C)) {
return new C();
}
this.isHappy = true;
}
const a = C();
```
2. 如果建立實例物件後又「重新」物件實字(object literal)建構子的 `prototype` 話,使用 `instanceof` 確認彼此是否為建構子 / 實例物件的關係時會出現問題(可往上查看範例)。(這樣子很麻煩,有時候專案大不小心搞壞建構子的 `prototype` 很難發現)
3. 手動更改建構子的 `prototype` 會打破原本實例物件的 `constructor` 自行指向建構子函式規則。

## 參考資料
1. [Constructors Are Bad For JavaScript](https://tsherif.wordpress.com/2013/08/04/constructors-are-bad-for-javascript/)
2. [[筆記] 談談 JavaScript 中的 function constructor 和 prototype 的建立](https://pjchender.blogspot.com/2016/06/javascriptfunction-constructorprototype.html)
3. [JS基本觀念:typeof vs instanceof](https://medium.com/@mengchiang000/js%E5%9F%BA%E6%9C%AC%E8%A7%80%E5%BF%B5-typeof-vs-instanceof-4dcb89e315df)
4. [instanceof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)