---
# System prepended metadata

title: C_三明治筆記
tags: [javascript]

---

[TOC]
# 三明治筆記
有問題可以直接在筆記上留言^ D ^

## Chapter02

### 變數

變數命名規範：只能用$、_及英文字母開頭，大小寫有區分

不能用保留字

#### 字串模板
```javascript!
var name = "Martha";
var introduction = `Hello ${name}! Nice to meet u.`; // 注意這裡用的是撇`，不是單引或雙引號

console.log(introduction);
// Hello Martha! Nice to meet u.
```

#### 轉型
```javascript!
var age = 28;
var age_str = String(age);

console.log(typeof age_str); // typeof不用加括號
// string

var age_num = Number(age_str);

console.log(typeof age_num);
// number

// 快速轉型小技巧
28+"" // 轉成字串
+"28" // 轉成數值
!!28  // 轉成布林值

// 但是如果+前面有數字則全部轉為字串後相加
console.log(5 + "28"); // 528
```
#### 數字
數字只有一種型別，沒有區分整數和浮點數
```javascript!
parseInt(); // 轉成整數
parseFloat(); // 轉成有小數點的數字
```
NaN也屬於數字

null屬於null，但檢查型別會出現object，是一個JS無法修復的bug
```javascript!
var sth_null = null;

console.log(typeof sth_null);
// object
```
#### 布林值與反向運算子
```javascript!
console.log(Boolean(age)); // true
console.log(Boolean(sth_null)); // false
console.log(!Boolean(age)); // false
console.log(!Boolean(sth_null)); // true
```

#### 物件object
```javascript!
var sth_object = {
  age: 28, // age稱為鍵，又稱為屬性property，28為值
  toy: "reindeer", // 分隔鍵值使用逗號,
};

// 兩種取值方法
console.log(sth_object["age"]); // 屬性名稱皆為字串
console.log(sth_object.age);

// 取不存在的屬性不會報錯，只會出現undefined
console.log(sth_object.whatever); 
// undefined

// 轉字串
console.log(String(sth_object));
// [object Object]
```
#### 陣列array，像是沒有屬性的物件
```javascript!
var sth_array = ["baby", 18];
console.log(sth_array[0]); // baby
console.log(sth_array.length); // 2

// 此時sth_object和sth_array所存放的其實是記憶體位置
[]===[]; // false 記憶體位置不同
{}==={}; // false 記憶體位置不同

var a = [];
var b = a;
a === b; // true a和b指到相同的記憶體位置
```

### 函式
用function宣告，裡面由return結束，return後面的程式碼會被忽略

### 運算子

#### 比較

兩側都是字串，則比較unicode編碼值
 
undefined會被轉為NaN，而NaN和任何數值比較都會變成false

#### 相依性與優先序

相依性：從什麼方向開始計算
優先序：誰先開始計算

### 強制轉型coercion

只會轉成三種類型
1. 字串
2. 數字
3. 布林

```javascript!
// 注意object的布林值是true
Boolean([]); // true
Boolean({}); // true
Boolean(function() {}); // true
```
物件轉字串、數字，由javascript本身執行一套名叫ToPrimitive的演算流程

### 判斷式
```javascript!
// switch
var a = 3;
switch (a) {
  case 1:
    console.log("a is 1");
    break; // 記得加break; 如果和函式一起使用也可以寫return
  case 2:
    console.log("a is 2");
    break;
  default: // 像是else的功能
    console.log("Idk what a is");
}
// Idk what a is

switch (a) {
  case 1:
  case 2:
    console.log("a is 1 or 2"); // 符合case1或2都會執行此行
    break;
  default: 
    console.log("Idk what a is");
}
```

### 三元運算子

條件判斷 ? 為true時執行 : 為false時執行;
物件內賦值時好用，其他時候就不要用
```javascript!
var whattype = "28" ? true : false;
console.log(whattype);
// true


// 物件內賦值
{
    name: condition ? "Jeremy":"Watson",
    sexual: condition ? "male":"female"
}
```

## Chapter03

### 編譯、直譯
編譯語言：寫完程式碼後就預先編譯

直譯語言：執行程式碼時才透過直譯器編譯，無法獨立執行需要有可編譯且執行結果的環境，通常由稱為引擎的工具提供，例如Chrome的引擎稱為V8，Firefox的引擎則叫SpiderMonkey

![image](https://hackmd.io/_uploads/SyoklnK7C.png)


### 執行環境 execution context
**引擎** 提供 **執行環境**

執行環境：任何程式碼可以被執行、讀取的地方，例如函式
* 全域執行環境
    - 創造this變數，this指向全域物件window
![image](https://hackmd.io/_uploads/rJYge2YQA.png)


    - 記憶體指派
    
* 函式執行環境
   - 函式被呼叫時才會產生
   - 在函式內產生arguments物件
   - 可以同時存在好幾個
* eval函式內的執行環境
   - 已不被使用，eval is evil

#### 執行環境堆疊
函式內有其他函式，先進後出

### 作用域

#### 語彙環境 lexical environment

 >   「這段程式碼寫在哪?」

#### 範疇 scope

>  「變數可以被使用的範圍」

函式範疇
```javascript
function whereiam() {
  var varinfunction = 5;
}
console.log(varinfunction); // 在函式範疇內就算是var也取不到
// ReferenceError: varinfunction is not defined
```
區塊範疇
區塊：{}裡面的範圍
```javascript
if (1) {
  var varinfunction = "var is here!";
  let letinfunction = "let is here!";
}
console.log(varinfunction);
// here!
console.log(letinfunction);
// ReferenceError: letinfunction is not defined
```
#### 作用域鍊 scope chain
找不到變數時向外層尋找變數的依據和順序
要知道外層是哪裡
和執行環境產生的先後順序不一定相同

#### 提升現象 hoisting
var變數只要在同一環境下有宣告，不需要在使用前先宣告才能使用，因為會先指派undefined給這個var變數，但let, const不會，所以不能使用

可以理解成：javascript幫你做了C語言中的宣告動作
```javascript!
console.log(a);
var a = 29;
// undefined // 而不是29
```

和[全域執行環境](#執行環境-execution-context)一開始做的事情(創造階段)有關：在執行程式碼前先指派了記憶體位置，保留到全域記憶體global memory/heap
```javascript!
// 函式內容在創造階段除了指派記憶體外也會存入內容，所以可以印出hello
hello();
function hello() {
  console.log("hello");
}
// hello

// 暫時無法使用的區域，稱為暫時性死區temporal dead zone (TDZ)
function hello() {
  console.log(hello); // 這裡是TDZ

  const hello = "hello!!";
}

hello();
console.log("secondary call");
hello();

// ReferenceError: Cannot access 'hello' before initialization
```


### var, let, const

var: 全域，可以重複宣告

let: 區域，不能重複宣告！

const: 區域，不能修改，只能被讀取，且宣告時一定要指派內容
```javascript
// 無法在全域讀取區域常數const
function hello() {
  // console.log(hello);
  if (true) {
    const hello = "hello!!";
  }
  console.log(hello);
}

hello();
console.log("secondary call");
hello();

// [Function: hello]
// secondary call
// [Function: hello]
```

```javascript
// 如果const的內容是物件，那所謂不能修改是指記憶體位置不能被修改
const sth_object = {
  age: 28,
  toy: "reindeer",
};

sth_object.height = 160; // 因此會看起來內容是可以被修改的
console.log(sth_object.height);
// 160
```

## Chapter04

判斷簡單型別使用 typeof
判斷複雜型別使用 [instanceof](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof) 或是取出 [constructor.name](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor)

### 物件型別
陣列、函式也是物件(函式物件)
可以用`Array.isArray`來判斷是不是陣列

### 原始型別
又稱純值(primitive type)或基本型別

ES6才出現的symbol，透過`Symbol()`產生
需要保證東西不一樣的時候才會使用，實作上幾乎不會使用到
```javascript!
let sth_symbol = Symbol("hey!");

console.log(sth_symbol);
// Symbol(hey!)
console.log(typeof first_symbol);
// symbol
```
兩個symbol是不同的值
```javascript!
let a = "hey!";
let b = "hey!";

console.log(a === b); 
// true

let first_symbol = Symbol("hey!");
let second_symbol = Symbol("hey!");

console.log(first_symbol === second_symbol);
// false
```

### 變數指派

#### 原始型別

存放的是內容，複製的也是內容

變數a指派給變數b後修改變數b的內容不影響變數a的內容，稱為傳值呼叫call by value

#### 物件型別

存放的是記憶體位置，複製的也是記憶體位置

傳參考呼叫call by reference

為什麼叫參考而不是指標？參考是指永遠不會改變的關係(這個變數永遠指向這個記憶體位置，且這個記憶體位置永遠存放這個內容)；指標可以更改指向的記憶體位置

```javascript
const mother; // 報錯，因為參考一定要綁定內容
const mother = "Linda";
let myName; // myName是一種指標，可以更改
```
#### Call by sharing

有時候看起來像傳值、有時候看起來像傳參考，所以額外取名為call by sharing

問題：要怎麼查詢記憶體位置？
javascript無法做到
```javascript!
// 傳入函式的是物件時，會變更到函式外的物件內容
let partylist = ["Jerry", "Andy", "Gary"];

function addName(list) {
  list.push("Yoda");
}

addName(partylist);
console.log(partylist);
// [ 'Jerry', 'Andy', 'Gary', 'Yoda' ]


// 傳入的是純值時不會變更
let partynum = 4;

function addNum(a) {
  a += 1;
  return a;
}
console.log(addNum(partynum));
// 5
console.log(partynum);
// 4 // 沒有變更到原本的值


// 函式內重新指派變數，卻又不會更改到函式外的物件
let partylist = ["Jerry", "Andy", "Gary"];

function addName(list) {
  list = ["Yoda"];
}

addName(partylist);
console.log(addName(partylist));
// undefined
console.log(partylist);
// [ 'Jerry', 'Andy', 'Gary' ] // 沒有被函式修改
```

## Chapter05

### 陳述式
不會產生值的地方，例如宣告、if/else、迴圈

### 表達式
執行完會產生值，例如函式呼叫、變數指派、運算式

#### 函式陳述式、函式表達式
```javascript
// 函式陳述式
// 因為提升的關係，可以在宣告前呼叫
a();

function a() {
    ...
}

// 函式表達式，直接把函式指派給一個變數
// 被提升的只有變數，不包含函式內容
a(); // 報錯

let a = function() {
    ...
}
```
函式是一種特殊的物件
```javascript
function a() {
    ...
}
// 等同於
{
  name: a,
  code: "..."; // 示意，非實際程式碼
}
    
console.log(a.name); 
// a
```
### 各種函式

#### 立即執行函式 IIFE, immediately invoked function expression
被創造後馬上執行的函式
有點像是強制把函式陳述式改成函式表達式

```javascript
function() {
    console.log("Hi");
};
// 報錯，JavaScript認為這個是陳述式，而陳述式必須要有名字

// 和運算子結合改成表達式的寫法
(function sayHi() {
    console.log("Hi");
})

// 當成一個物件使用
// 取用name
(function sayHi() {
    console.log("Hi");
}).name;

// 呼叫這個函式
(function sayHi() {
    console.log("Hi");
})();
// Hi

// 可以傳入參數
(function sayHi(name) {
  console.log(`Hi, ${name}!`);
})("Jeremy");
// Hi, Jeremy!

// 改成箭頭函式的寫法
((name) => {
  console.log(`Hi, ${name}!`);
})("Jeremy");
// Hi, Jeremy!
```

#### 一級函式
一個語言有一級函式的特性，代表函式被視為變數，可以作為參數傳入另一個函式，也可以作為另一個函式的回傳值

```javascript
// 用函式作為參數及回傳值
function eatFunction(fn, data) {
    return fn(data);
};
```

#### 高階函式 HOF, high order function
可以接收函式作為參數，或是回傳函式作為輸出

透過傳入函式達到2*3的效果，如果只是2+3的效果，傳入的函式就只是一個單純的callback function
```javascript
// 利用自行實作Array.map來了解什麼叫做透過函式傳入另一個函式
function arrayMap(fn, array){
    ...
}
```
**forEach**代替for迴圈對陣列元素做事情
```javascript
let array = [1, 2, 3];

// for
for(let i = 0; i < array.length; i++){
    // ...
    console.log(element);
}

// forEach
array.forEach(function (element) {
    // ...
    console.log(element);
})
```
#### 箭頭函式
```javascript
const add = (a, b) => a + b; // 這裡的a+b就是回傳值
const add = (a, b) => (a + b); // 當有多行表達式時使用

const add = (a, b) => { a + b } // 無法作用
const add = (a, b) => { return a + b } // 無法作用

const add = (a) => (b) => a + b;
// 等同於
const add = function(a) {
    return function(b) {
        return a + b;
    }
}
```
**arguments**
函式裡的arguments物件是用來接收傳入函式的引數
長得很像陣列，稱為array-liked

但是箭頭函式沒有arguments!!!也沒有this!!!
```javascript
function add(a, b){ // argument[0] = a, argument[1] = b
    //...
}
```
#### 回呼函式 callback function
在函式裡面執行的另一個函式
確保某段邏輯在另一段邏輯之後被執行

### 閉包 closure
起因來自於函式引用到外部函式的變數

嘗試透過範疇鍊向外尋找變數，發現外部的函式有這個變數，所以JavaScript會暫時保留這個外部的函式的記憶體，這樣才能存取這個變數

這個被暫時保留的記憶體空間就是閉包

![image](https://hackmd.io/_uploads/Sk0ljt3Q0.png)

![image](https://hackmd.io/_uploads/SkHboY3m0.png)


當內部函式被回傳後，除了自己本身的程式碼外，也可以取得內部函式「當時環境」的變數值，記住了執行當時的環境
> https://ithelp.ithome.com.tw/articles/10193009

避免修改到全域變數，但又要修改變數

```javascript
// 沒有使用閉包的樣子
var count = 0; // 宣告全域變數，重新呼叫函式時才能保留結果

function counter(){
  return ++count; 
}

console.log( counter() );   // 1
console.log( counter() );   // 2
console.log( counter() );   // 3


// 使用閉包的樣子
/// 寫法1
function counter(){
  var count = 0;

  function innerCounter(){
    return ++count;
  }

  return innerCounter;
}
/// 寫法2
function counter(){
  var count = 0;

  return function(){
    return ++count;
  }
}
///寫法3
var counter = () => {
  var count = 0;
  return () => ++count;
}

///執行
var countFunc = innerCounter; // ReferenceError: innerCounter is not defined
var countFunc = counter(); // countFunc = innerCounter
var countFunc2 = counter();

console.log( countFunc() ); // 執行innerCounter()，修改innerCounter外部的變數count
// 1
console.log( countFunc() );   
// 2
console.log( countFunc() );   
// 3

// countFunc和countFunc2不會互相影響
console.log( countFunc2() );   // 1
console.log( countFunc2() );   // 2
```

### 其餘參數
需要一次處理多個參數且不確定參數數量時使用
![image](https://hackmd.io/_uploads/S1MGjFnQR.png)
```javascript
// 使用rest來取用其餘參數形成的陣列
...rest

rest.forEach
```

## Chapter06

* **JavaScript Runtime Environment(JRE)**
包含：
JavaScript引擎
Web API
Event Queue
Event Table
Event Loop

* **Event Queue**
非同步的function被儲列的位置
先進先出的運行流程(和堆疊的先進後出不同)
    - MacroTask
    在Event Queue裡面等待被以非同步方式執行的事情
    對瀏覽器來說，JavaScript引擎也是一個MacroTask
    - MicroTask
    Promise產生的then或catch裡面要執行的事情
    在MicroTask Queue裡面等待
    - MicroTask會在每個MacroTask結束後被執行
```javascript
console.log("Start");

setTimeout(() => { console.log("Macro"); });
Promise.resolve().then(() => { console.log("Micro1"); });
Promise.resolve().then(() => { console.log("Micro2"); });

console.log("End");

// Start
// End
// Micro1
// Micro2
// Macro
```
* **Event Table**
和event queue互相搭配，紀錄在非同步的目的達成後，有哪些函式或事件要執行
例如setTimeout裡給定的函式和秒數
setTimeout在event table計時，計時完成後送到event queue裡

* **Event Loop**
負責無時無刻不檢查主執行環境是否是空的、event queue是不是空的
    - 優先執行MicroTask Queue的任務
    

```javascript
// 印出333
for (var i = 0; i < 3; i++) {
    setTimeout(function(){
        console.log(i)
    }, 1000)
}

// 印出012
for (let i = 0; i < 3; i++) { // 不懂
    setTimeout(function(){
        console.log(i)
    }, 1000)
}

for (var i = 0; i < 3; i++) {
    (function(x) {
        setTimeout(function(){console.log(i)}, 1000)
    })(i)
}
```
### Promise

```javascript
// new關鍵字用來創造物件
// 和new搭配的函式(這裡是Promise)稱為
// 建構函式(Constructor function)
let promise = new Promise(function(resolve, reject){
    // ...
})

//  resolve和reject也是函式
//  當行為成功時呼叫resolve
//  當行為失敗時呼叫reject
let promise = new Promise(function(resolve, reject){
    // ...
    if (user.name === "Watson") {
        resolve("Hi");
    }else{
        reject("Who r u");
    }
})
```

#### .then, .catch

```javascript
// 接續上一塊程式碼
// 分別寫了兩個函式要處理promise成功與失敗時的處理
function invitePeople(people){  // 成功時邀請
    console.log(people);
    return people + "OK";
}

function rejectPeople(reason){  // 失敗時拒絕
    console.log(reason);
    return reason + "No";
}

// 用then讓這兩個函式去接promise的結果
promise.then(invitePeople, rejectPeople);
// 或是用catch
promise.then(invitePeople)
       .catch(rejectPeople);

// 等同於將promise的結果傳入這兩個函式
// invitePeople("Hi")
// rejectPeople("Who r u")

promise.then(invitePeople)
       .then(invitePeople) 
// 把上一個invitePeople的return值傳入第二個then的函式
```
#### Promise.resolve("就是要邀請"), Promise.reject("就是要拒絕")
用Promise.resolve或Promise.reject來直接取得成功或失敗的結果
```javascript
// "就是要邀請"直接傳入invitePeople
let everybodyInParty = Promise.resolve("就是要邀請");
everybodyInParty.then(invitePeople); 

// "就是要拒絕"直接傳入rejectPeople
let nobodyInParty = Promise.reject("就是要拒絕");
nobodyInParty.catch(rejectPeople); 
```

#### Promise.all, Promise.race
```javascript
let promise1 = Promise.resolve("1");
let promise2 = Promise.resolve("2");
let promise3 = Promise.reject("QQ");

// Promise.all
// 陣列中所有Promise都成功才會回傳這些Promise的成功結果
let successGroup = Promise.all([promise1, promise2])
successGroup.then(invitePeople).catch(rejectPeople);
// [ '1', '2' ]

// 否則回傳失敗的結果
let failGroup = Promise.all([promise1, promise2, promise3])
failGroup.then(invitePeople).catch(rejectPeople);
// QQ


// Promise.race
// 陣列中只要有Promise成功或失敗，回傳第一個成功或失敗的結果
let raceGroup = Promise.race([promise1, promise2, promise3]);
raceGroup.then(invitePeople).catch(rejectPeople);
// 1
```
### async, await
#### async
==async宣告的函式一定會回傳Promise==
```javascript
async function addPerson() {
  return "FangFang"; // return的是一個Promise
}

console.log(addPerson());
// Promise { 'FangFang' }

addPerson().then((name) => { // 所以要用then來取用結果
  console.log(name);
});
// FangFang
```
#### await
await只能用在async關鍵字宣告的function內
await等待的必須是另一個Promise
```javascript
let vip = Promise.resolve("Jami");

async function addPerson() {
  let name = await vip; // await等待另一個Promise (這裡是vip)

  return name;
}

addPerson().then((name) => {
  console.log(name);
});
// Jami
```
#### try...catch
try區塊也可以透過throw直接中止程式碼並跳到catch區塊
```javascript
try {
    throw "Hahahaha";
    
} catch(error) {
    console.log(error); // Hahahaha

}
```
搭配async/await

```javascript
let fakeguy = new Promise(function (resolve) {
  throw "I am fake guy!!"; // 讓fakeguy函式丟一個錯誤
  resolve("I am good guy!!"); // 這段就不會被讀到了
});

async function addPerson() {
  try {
    let name = await fakeguy; // 這邊就會丟一個錯誤到catch
    console.log(name);
      
  } catch (error) {
    console.log(error); // catch接到錯誤後把error印出
  }
}

addPerson();
// I am fake guy!!
```

![image](https://hackmd.io/_uploads/BkeD33U40.png)

## Chapter07

利用函式建構一個物件，名稱以大寫開頭以區分一般的函式
使用this
```javascript
function Bag(){ // 大寫開頭
    this.owner = "CReticulata",
    this.item = "Surface pro",
}

const blackBag = new Bag(); // 不加new的話就會直接執行這個函式
console.log(blackBag);
// { owner: "CReticulata", item: "Surface pro" }
```
物件可以使用物件函式是來自於**繼承**
可以透過in來查詢繼承而來的屬性，hasOwnProperty不行

### 解構賦值
ES6才有的

物件的解構賦值
```javascript
const userInfo = {
  name: "Cre",
  height: 157,
};
// 變數名稱一定要是物件內原本有的key
const { name, height } = userInfo;
console.log(height);
// 157

// 設定別名
const { name: nameOfUserA, height } = userInfo;
console.log(nameOfUserA);
// Cre
```

### 複製物件

淺拷貝是為了避免無限複製迴圈的問題
![image](https://hackmd.io/_uploads/BJ3OtHxHA.png)



#### 展開運算子`...`
```javascript
const userInfoA = {
  name: "Cre",
  height: 157,
};
const userInfoB = { ...userInfoA };
console.log(userInfoB);
// { name: 'Cre', height: 157 }


// 一樣的屬性會被後者蓋掉
const userInfoA = {
  name: "Cre",
  height: 157,
  age:18,
};
const userInfoB = {
  name: "Jeremy",
  height: 180,
};
const userInfoC = { ...userInfoA, ...userInfoB };
console.log(userInfoC);
// { name: 'Jeremy', height: 180, age: 18 }
```
![image](https://hackmd.io/_uploads/Sy_KtrlB0.png)

#### Object.assign
```javascript
const userInfoA = {
  name: "Cre",
  height: 157,
  
};
const userInfoB = {
  age:18,
};

// Object.assign(目標物件, 來源物件)
const userInfoC = Object.assign(userInfoA, userInfoB)
console.log(userInfoC);
// { name: 'Cre', height: 157, age: 18 }
```
#### 深拷貝
使用JSON或是Object.create
### 屬性描述器
(實作上比較不會用到)
#### Object.getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor(物件, "屬性")
```javascript
const userInfoA = {
  name: "Cre",
  height: 157,
};
const description = Object.getOwnPropertyDescriptor(userInfoA, "name");
console.log(description);
// { 
//     value: 'Cre', 
//     writable: true, 
//     enumerable: true, 
//     configurable: true 
// }
```
* value: 值
* writable: 可否被變更
* enumerable: 巡訪時能否被列出
* configurable: 這些屬性描述器能否被修改(能不能用Object.defineProperty修改)
    ==false後要怎麼改回來？==
* get: 物件屬性上的getter函式，定義**取用**屬性時的行為
* set: 物件屬性上的setter函式，定義**指派**屬性時的行為
    描述器有設定get或set的話會變成存取描述器(accessor descriptor)，否則為資料描述器(data descriptor)
![image](https://hackmd.io/_uploads/HJf5FSgrA.png)
```javascript
// 如果address前不加底線區隔
class Person {
    constructor(name) {
        this.name = name;
        this.address = 'Taipei';
    }
    get address(){
        console.log('get');
        return this.address;
    }
    set address(value){
        console.log('set');
        this.address = value;
    }
}

let kuro = new Person('Kuro')
kuro.address
// 瀏覽器會跑get
// get
// 'Taipei'
// 但node會噴錯
console.log(kuro.address);
// 瀏覽器跟node都會噴錯


// 所以內容封裝用底線區隔
class Person {
  constructor(name) {
    this.name = name;
    this._address = "Taipei";
  }
  get address() {
    console.log("get");
    return this._address;
  }
  set address(value) {
    console.log("set");
    this._address = value;
  }
}
let kuro = new Person("Kuro");
console.log(kuro.address);
// get
// Taipei
console.log(kuro._address);
// Taipei
```

#### Object.defineProperty
修改屬性描述器的內容

#### Object.getOwnPropertyNames
得到所有屬性的名字，就算enumerable為false也可以



### This
binding綁定:指向哪一個物件

#### 預設的綁定
函式被直接呼叫，this指到全域
```javascript
function whereAmI() {
  console.log(this.a);
}

var a = "global";
whereAmI();
// 在NodeJS會出錯
// TypeError: Cannot read properties of undefined (reading 'a')

// 瀏覽器
// global
```

#### 隱含的綁定
this指向用來呼叫這個函式的物件
```javascript
function whereAmI() {
  console.log(this.message);
}
const playground = {
  message: "hey!",
  whereAmI: whereAmI, // 這裡要設置
};

playground.whereAmI(); // 這裡的whereAmI()是執行playground裡面的屬性
// hey!

whereAmI();
// undefined
```
隱含綁定的消失
```javascript
const playground = {
  message: "hey!",
  whereAmI: function () {
    console.log(this.message);
  },
};

const whereAmI = playground.whereAmI; // this指向全域了
whereAmI(); 
// TypeError: Cannot read properties of undefined (reading 'message')
```
#### 明確的綁定
.call(指定this指向的物件, 傳入該函式的參數...)
.apply(指定this指向的物件, 以陣列方式傳入該函式的參數...)
```javascript
function whereAmI() {
  console.log(this.message);
}
const playground = {
  message: "hey!",
};
whereAmI.call(playground);  // hey!
whereAmI.apply(playground); // hey!

// 硬綁定
// 使用.bind
const bindLocation = whereAmI.bind(playground);
bindLocation();
```
![image](https://hackmd.io/_uploads/ryysYBeB0.png)


#### new運算子的綁定
參考第七章最前面的[說明](https://hackmd.io/PlVO4B_BRl-lsHURNJcxLg#Chapter07)

#### 箭頭函式的this
箭頭函式的this會依照這個箭頭函式的程式碼實際位置而定

看不懂setTimeout的例子XD
```javascript
const userInfo = {
  name: "Helen",
  changeName: function () {
    this.name = "Jeremy";

    function changeNameAgain() {
      this.name = "Watson";
      console.log(this);
      console.log(userInfo.name);
    }

    setTimeout(changeNameAgain, 1000);
  },
};

userInfo.changeName();
// Timeout {
//   ...
// }
// Jeremy

// 使用箭頭函式
const userInfo = {
  name: "Helen",
  changeName: function () {
      this.name = "Jeremy";

      const changeNameAgain = () => {
          console.log(this);
          this.name = "Watson";
          console.log(userInfo.name);
      }

      setTimeout(changeNameAgain, 1000);
  },
};

userInfo.changeName();
// { name: 'Jeremy', changeName: [Function: changeName] }
// Watson
```

## Chapter08
### 原型
原型：物件之間特殊的連結
`[[prototype]]`
```javascript
const objectA = {
  name: "object A",
};

// Object.create執行完後收到一個空物件，並以傳入的物件為原型
const objectB = Object.create(objectA);

console.log(Object.getPrototypeOf(objectB));
// { name: 'object A' }
console.log(objectB.__proto__);
// { name: 'object A' }
console.log(objectB);
// {}
console.log(objectB.name);
// object A
```
問題
```javascript
function userInfo(name) {
  this.name = name;
}

userInfo.prototype.setAge = function (age) {
  this.age = age;
};

// 用Object.create創造objectB
const objectB = Object.create(userInfo);

console.log(Object.getPrototypeOf(objectB));
// [Function: userInfo]
objectB.setAge(18);
// TypeError: objectB.setAge is not a function


// 用new
const objectB = new userInfo("hey");

console.log(Object.getPrototypeOf(objectB));
// { setAge: [Function (anonymous)] }
objectB.setAge(18);
console.log(objectB.age);
// 18


// 用Object.create的話要這樣做
const objectB = Object.create(userInfo.prototype);
```

### 原型鍊
```javascript
const array = []
array.__proto__.__proto__ === Object.prototype
// true
```
![image](https://hackmd.io/_uploads/BJh0dNtBC.png)


```javascript
function Human(weight, height) {
  this.weight = weight;
  this.height = height;
}

function User(name) {
  this.name = name;
}

Human.prototype.getHumanHeight = function () {
  return this.height;
};

User.prototype.getName = function () {
  return this.name;
};

console.log(User.prototype);
// { getName: [Function (anonymous)] }


// 利用Object.create將Human設為User的前代類別
// 意即User.prototype被修改為一個新的空物件，而這個物件的原型指向Human
User.prototype = Object.create(Human.prototype);
console.log(User.prototype);
// Human {}
```

#### constructor
指回這個物件本身
```javascript
console.log(User);
// [Function: User]
console.log(User.prototype.constructor);
// [Function: User]


// 用Object.create會把constructor指向的東西改掉
User.prototype = Object.create(Human.prototype);
console.log(User.prototype.constructor);
// [Function: Human]

// 所以有些人會把他設定回來
User.prototype.constructor = User;
console.log(User.prototype.constructor);
// [Function: User]
```

讓前代類別的內容出現在透過後代類別的建構函式所產生的物件上
```javascript
function User(name, weight, height) {
  this.name = name;
  Human.call(this, weight, height)
}
```
### class
```javascript
function Human(weight, height) {
  this.weight = weight;
  this.height = height;
}

Human.prototype.getHumanHeight = function () {
  return this.height;
};

// 換成用class
class Human {
  constructor(weight, height) {
    this.weight = weight;
    this.height = height;
  }

  getHumanHeight() {
    return this.height;
  }
}
```
#### extends
用extends來繼承
```javascript
class User extends Human {
  constructor(name, weight) {
    super(weight); // super一定要出現不然會報錯如下
      // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    this.name = name;
  }
}

const personA = new User("Amy");
console.log(personA);
// User { weight: undefined, height: undefined, name: 'Amy' }
```

### static
透過static定義靜態方法
靜態方法？只能在該class取用
```javascript
// 不用static，可以在後代取用
class Human {
  constructor(weight, height) {
    this.weight = weight;
    this.height = height;
  }

  getHumanHeight() {
    return this.height;
  }
}

class User extends Human {
  constructor(name, weight, height) {
    super(weight, height);
    this.name = name;
  }
}

const personA = new User("OK", 160, 170);
console.log(personA.getHumanHeight());
// 170


// 用static，後代不能取用
class Human {
  constructor(weight, height) {
    this.weight = weight;
    this.height = height;
  }

  static getHumanHeight() {
    return this.height;
  }
}

class User extends Human {
  constructor(name, weight, height) {
    super(weight, height);
    this.name = name;
  }
}

const personA = new User("OK", 160, 170);
console.log(personA.getHumanHeight());
// TypeError: personA.getHumanHeight is not a function

// 但繼承的class可以使用
User.getHumanHeight()
// undefined
```
### 原始型別包裹物件 Wrapper Object

![image](https://hackmd.io/_uploads/S1T1FNYrA.png)

不加new也可以用，是因為JavaScript會在這一瞬間幫你把這個字串轉成對應型別的物件
取用完屬性之後，這個包裹物件就消失了
```javascript
const string = String("something");
console.log(string.length);
// 9
console.log(string.indexOf("e"));
// 3
```