[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

### 執行環境 execution context
**引擎** 提供 **執行環境**
執行環境:任何程式碼可以被執行、讀取的地方,例如函式
* 全域執行環境
- 創造this變數,this指向全域物件window

- 記憶體指派
* 函式執行環境
- 函式被呼叫時才會產生
- 在函式內產生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會暫時保留這個外部的函式的記憶體,這樣才能存取這個變數
這個被暫時保留的記憶體空間就是閉包


當內部函式被回傳後,除了自己本身的程式碼外,也可以取得內部函式「當時環境」的變數值,記住了執行當時的環境
> 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
```
### 其餘參數
需要一次處理多個參數且不確定參數數量時使用

```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!!
```

## 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
```
### 複製物件
淺拷貝是為了避免無限複製迴圈的問題

#### 展開運算子`...`
```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 }
```

#### 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)

```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();
```

#### 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
```

```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

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