---
title: 'Advanced Javascript'
disqus: hackmd
---
Advanced Javascript
===
## Table of Contents
[TOC]
###### tags: `Javascript`
Javascript Foundation
---
### 一、Call Stack && Memory Heap(內存堆)
#### 1. Call Stack 與 Stack Overflow
```javascript=
// When a function calls itself
// it's called Recursion
function inception() {
inception()
}
inception()
// return Maximun call stack size exceeded
```
圖像化觀察 Call Stack 與 Web Api 交互的網站
http://latentflip.com/
#### 2. Memory Heap 與 garbege collection
- 沒有被參照到的 object 就會被垃圾回收機制回收掉

- 當這個函數被掉用完後,human這個變數就會被垃圾回收掉
```javascript=
function subtraction(num) {
var human = {
first: 'Andrei',
last: 'Neagoie'
}
return 2
}
```
#### 3. Memory Heap 與 Memory leak
- 用越來越多的數據去填充內存堆,垃圾搜集機制不會起作用,因為 for 迴圈一直執行它。
```javascript=
let array = []
for (let i = 5; i > 1; i++) {
array.push(i-1)
}
```
#### 4. 常見的記憶體洩漏
- Global variable
使用越多全域變數,會佔用越多記憶體內存
- event listener
如果你一直增加監聽器而不刪除他,特別是在 SPA 網頁用戶來回移動時,會一直創造新的監聽器。內存就會增加越來越多的事件監聽器
- setInterval
setInterval 除非我們清除他,不然他都不會被垃圾回收
### 二、Execution Context
```javascript=
function printName() {
return 'Andrei'
}
function findName() {
return printName()
}
function sayMyName() {
return findName
}
sayMyName()
```
- 每一個 function 會產生一個 Execution Content,並以fist in last out 的方式一個一個加入 stack 中做執行(如下圖),等執行完後,stack 會清空,瀏覽器中的 Execution Content 又只剩下 Global Execution Content

- 瀏覽器一開始就會建立一個 Global Execution Content,裡面會有 Global Object 和 this

### 三、Lexical Environment
- Lexical Environment 指的是你在哪裡寫下程式碼
- 可以把 Lexical Environment 想成 Execution Context 創建時生成的星球裡面的東西
-

### 四、Hoisting

- Hoisting 是什麼呢?
- Hoisting 並不是時記得把程式碼移到最前面,而是 Javascript Engine 在 execution context 分配**內存的動作**。
- 如果 Excution Context 裡有 var 或 function 開頭的宣告,Javascript Engine 就會去 Memory Heap 內建立相對應的內存,
- 如果是 var Name,會在內存裡建立一個 Name 的位置,但因為尚未賦予值,因此 Name 的變數會先預設為 undefined
- 如果是 function 開頭的宣告,則會把所有 function 內容存到內存內
就像下面的例子,即使我在尚未宣告變數 name 和 function sayHi 前,我就可以讀取這個變數及函式
```javascript=
console.log(Name) // undefine
console.log(sayHello) // undefine
console.log(sayHi()) // 'hi'
var name = 'Paggy'
// function declaration
var sayHello = function() {
console.log('Hello')
}
// function expresion
function sayHi() {
console.log('Hi')
}
```
- 如何避免 Hoisting?
- 變數使用 **let** 和 **const** 宣告
- 函式使用 **function expresion** 且使用 let 或 const 宣告
- 使用 IIEF,當使用立即函式時,JS 分配記憶體的動作看到 (function(){..})() 的開頭不是 function,就不會將 IIEF 提升
- **每一個 Execution context 都會有 Hoisting 出現**,像是一開始的 Global Execution Context 就包含了 Hoisting
- 下面的範例中,foodThoughts 這個 function 被呼叫時,他的 Execution context 伴隨著 Hoisting,他看到 var fovorFood,因此先將他放入內存中,暫時給予 undefined 的值
- 等到下一個 favorFood = 'sushi' 後,下一段 console.log favorFood 的值就會是 sushi
```javascript=
var favorFood = "grapes"
function foodThoughts() {
console.log(`origin favorite food is ${favorFood}`)
// origin favorite food is undefine
var favorFood = 'sushi'
console.log(`new favorite food is ${favorFood}`)
// new favorite food is sushi
}
foodThoughts()
```
### 五、Function Invocation
### 六、arguments Keyword
- 每一個函式創造出的 exection contect 裡面都有一個 argument

- argument 並不是一個 array,他並不能使用使用 array 的方法遍歷

- 可以使用 Array.from(arguments) 或解構參數的方式將 argument 變成 array
```javascript=
function a(am,bm) {
console.log(Array.from(arguments))
}
function a(...args) {
console.log(args)
}
```
### 七、Variable Environment
- execution context 會自成一個自己的變數環境,不受其他 execution context 影響

```javascript=
function two() {
var isValid; // undefined
}
function one() {
var isValid = true // true
two()
}
var isValid = false //false
one()
```
### 八、Scope Chain
function 透過 scope chain 去拿取父層的變數,一直會往外找到 global exection context

```javascript=
function sayName() {
var a = 'a'
return function findName() {
var b = 'b'
return function printName() {
var c = 'c'
return 'Andrei'
}
}
}
sayMyName()()()
```
### 九、Function Scope vs Block Scope
scope 意味著我們可以訪問哪些變數
- function scope 指的是在 function { } 內的範圍,在 function 內的變數就無法讓外部訪問
```javascript=
function a() {
var secret = '12345'
}
console.log(secret) // Uncaught ReferenceError
```
- Block scope 指的是除了 function 外,其他的 { } 範圍,但這僅限於 ES6 後使用 let, const 來說。因為如果是使用 var,在 { } 外仍然可以訪問到 { } 內的變數 ⇢ **建議大部分都用 let 和 const 來築成 block scope**
```javascript=
if(5>4) {
var secret = '12345'
}
console.log(secret) // 12345
if(5>4) {
var secret2 = '12345'
}
console.log(secret2) //Uncaught ReferenceError
```
```javascript=
function loop() {
for(var i = 0; i < 5;i++) {
console.log(i)
}
console.log('final',i) // 1, 2, 3, 4, final 5
}
function loop() {
for(let i = 0; i < 5;i++) {
console.log(i)
}
console.log('final',i) //Uncaught ReferenceError
}
```
### 十、Global Variables
避免使用過多的全域變數,使用全域變數可能造成
- 過多的全域變數會造成 memory leaks
- 變數命名衝突
### 十一、IIFE
使用立即函式,可以避免污染全域環境
### 十二、this Keyword
#### 1. object.someFunc(this)
物件內的函式,在函式內可以透過 this 訪問物件內的其他屬性或方法
- this 參照的對象主要是看哪一個物件呼叫他 
```javascript=
const obj = {
name: 'Bitty',
other: {
name: 'Abby',
singA() {
return 'lalala ' + this.name
}
},
}
obj.other.singA() // lalala Abby
```
- this 讓方法可以讀取物件
```javascript=
const obj = {
name: 'Billy',
sing() {
return 'lalala ' + this.name
},
singAgain() {
return this.sing() + '!'
}
}
obj.sing()
```
- 不同物件可以執行同樣的程式碼
```javascript=
function importantPerson() {
console.log(this.name)
}
const name = 'Sunny'
const obj1 = {
name: 'Cassy',
importantPerson: importantPerson
}
const obj2 = {
name: 'Jacob',
importantPerson: importantPerson
}
importantPerson() // Sunny
obj1.importantPerson() // Cassy
obj2.importantPerson() // Jacob
```
- Javascript 中,Lexical Scope 決定我們可以取用的變數,不會因為變數是從哪裡調用它而有所不同,但是,this 不管 Lexical Scope Environment,他只看是誰呼叫他 ⇢ this屬於 dynamic scope
#### 2. 如何解決 this 的動態 dynamic scope
```javascript=
const obj = {
name: 'Billy',
sing: function() {
console.log('a', this)
var anotherFunc = function() {
console.log('b', this)
}
anotherFunc()
}
}
obj.sing()
// a, {name: 'Billy', sing: ƒ}
// b, window....
```
- 使用 arrow function,arrow function 是 lexically 綁定的。他會綁定在該 lexically enviroment 的物件上
```javascript=
const obj = {
name: 'Billy',
sing() {
console.log('a',this)
var anotherFunc = () => {
console.log('b',this)
}
anotherFunc()
}
}
obj.sing()
// a, {name: 'Billy', sing: ƒ}
// a, {name: 'Billy', sing: ƒ}
```
- 使用 bind 將 this 綁定到 obj 上
```javascript=
const obj = {
name: 'Billy',
sing() {
console.log('a',this)
var anotherFunc = function() {
console.log('b',this)
}
return anotherFunc.bind(this)
}
}
obj.sing()()
// a, {name: 'Billy', sing: ƒ}
// a, {name: 'Billy', sing: ƒ}
```
- 在該詞彙環境下,另宣告一個變數指向 this,因為在宣告的當下,this 還是在 obj 裡面
```javascript=
const obj = {
name: 'Billy',
sing: function() {
console.log(this)
const self = this
var anotherFunc = function() {
console.log(self)
}
anotherFunc()
}
}
obj.sing()
// a, {name: 'Billy', sing: ƒ}
// b, {name: 'Billy', sing: ƒ}
```
#### 3. this 練習
箭頭函式只看詞彙環境 ⇢ 尖頭個性比較直,只會看當下的環境做事
一般函式看誰呼叫它 ⇢ 一般人,位高的人叫他做事他就做
```javascript=
var b = {
name: 'jay',
say() {console.log(this)}
}
var c = {
name: 'jay',
say() {return function() {console.log(this)}}
}
var d = {
name: 'jay',
say() {return () => console.log(this)}
}
b()
/* {
name: 'jay',
say() {console.log(this)}
}*/
c()() // window
// 當執行 c() 返回一個 function() {console.log(this)}
// 當在執行 (function() {console.log(this)})() 呼叫他的就會是 window 全域環境
d()()
/* {
name: 'jay',
say() {console.log(this)}
}*/
// 箭頭函式只看詞彙環境做事
```
### 十三、call(), apply(), bind()
- call(), apply() 用來呼叫函式
- bind() 則回返回一個函式
```javascript=
function a() {
console.log('hi')
}
a.call() // hi
a.apply() // hi
a.bind() // function a() {...}
```
a() 現在習慣的函式呼叫,算是一種簡寫
- 使用 call 將 this 成其他物件, ex: 魔法師治療自己,魔法師也治療弓箭手
```javascript=
const wizard = {
name: 'Merlin',
health: 50,
heal() {
return this.health = 100
}
}
const archor = {
name: 'Robin Hood',
health: 30
}
console.log('1', archor)
wizard.heal.call(archor)
console.log('2', archor)
```
- call() 和 apply() 的用法主要差別在參數,call() 直接把參數代在對象物件後面,apply() 則需要在對象物件後面寫成陣列
```javascript=
const wizard = {
name: 'Merlin',
health: 50,
heal(num1, num2) {
return this.health += num1 + num2
}
}
const archor = {
name: 'Robin Hood',
health: 30
}
console.log('1', archor)
wizard.heal.call(archor, 100, 30)
console.log('2', archor)
wizard.heal.apply(archor, [100, 40])
console.log('3', archor)
```
- bind() 則會在綁定物件及參數後返回一個函式
```javascript=
const wizard = {
name: 'Merlin',
health: 50,
heal(num1, num2) {
return this.health += num1 + num2
}
}
const archor = {
name: 'Robin Hood',
health: 30
}
console.log('1', archor)
const healArchor = wizard.heal.bind(archor, 100, 30)
console.log(healArchor)
healArchor()
console.log('2', archor)
```
### 十四、bind() and currying
使用 currying 重用一段程式碼,給他部分參數,並創建可擴展的函數
```javascript=
// function currying
function multiply(a, b) {
return a*b
}
let multiplyByTwo = multiply.bind(this, 2)
console.log(multiplyByTwo(4)) // 8
let multiplyByTen = multiply.bind(this, 10)
console.log(multiplyByTen(4)) // 40
```
### 十五、Context vs Scope
Types In Javascript
---
### 一、Array.isArray()
如果用 typeof 去檢查 array,會顯示 Object
那有沒有什麼方法可以判斷型別為陣列呢?
可以使用 Array.isArray() 來判斷
### 二、Pass By Value vs Pass By Reference
#### 1. 陣列拷貝 concat
```javascript=
const a = [1,2,3]
const b = [].concat(a)
```
#### 2. 物件拷貝
- shollow copy 淺拷貝
```javascript=
const a = {
a: 'a',
b: 'b',
c: 'c',
}
const b = Object.assign({}, a)
const c = {...a}
```
- deep copy 深拷貝
但要注意,使用 JSON 深拷貝的方法有效能上的問題
```javascript=
const a = {
a: 'a',
b: 'b',
c: {
say: 'cccc'
}
}
const b = JSON.parse(JSON.stringify(a))
```
- 如何比較來自不同位址的物件,但物件property是相同的
```javascript=
var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false
// Solution
var eq = JSON.stringify(user1) === JSON.stringify(user2)
alert(eq); // gives true
```
- function 內的參數是 passed by value
```javascript=
const number = 100
const string = "Jay"
let obj1 = {
value: "a"
}
let obj2 = {
value: "b"
}
let obj3 = obj2;
function change(number, string, obj1, obj2) {
number = number * 10;
string = "Pete";
obj1 = obj2;
obj2.value = "c";
}
change(number, string, obj1, obj2);
//Guess the outputs here before you run the code:
console.log(number); // 100
console.log(string); // Jay
console.log(obj1.value); // a 因為 argument 是傳值,所以function(..argument) 傳過來的 argument 是拷貝一份新的值過來,所以不會影響到原本的物件
```
Colsures and Prototype
---
### 一、Functions are Objects
- 在 Javascript 中,Function 也是 Object 的一種,一種可以呼叫的 Object
- callable Object 有特別的功能,如:bind, apply, call...
- 因為 Function 是 Object,所以他可以做很多有趣的應用,例如:跟物件一樣當成參數傳出去、可以把它當成資料存起來
### 二、First Class Citizens
### 1. function is first class citizens in JS
- 函式可以分配給變數或屬性
```javascript=
var vari = function
var obj = {
fn: function() {}
}
```
- 函式可以作為參數傳遞給函式
```javascript=
function a(fn) {
fn()
}
a(function() {console.log('Hello World')})
```
- 函式可以作為其他函數的值返回
```javascript=
function b() {
return function c() {console.log('bye')}
}
var d = b()
d()
```
### 三、Extra Bits: Functions
在 function 的參數裡加上預設值,以免參數出現 undefined 的狀況
```javascript=
function a(param = 6) {
return param
}
a()
```
### 四、Higher Order Functions
#### 1. 什麼事 HOF
- 把函式當作參數傳入另一個函式
- function 回傳另一個 function
=> 抽象函示:把內容抽象化放到函式裡
#### 2. Keep code DRY(Don't repeat yourself)
- 下面程式兩個函式幾乎執行的動作都是一樣的,只有跑 for 迴圈時的 loop 不同,該怎麼遵守 DRY 原則?
```javascript=
function letAdminLogin(name) {
let array = [];
for (let i = 0; i < 50000; i++) {
array.push(i)
}
return 'Access Granted to ' + name;
}
function letUserLogin() {
let array = [];
for (let i = 0; i < 100000; i++) {
array.push(i)
}
return 'Access Granted to ' + name;
}
```
- 使用 HOF 將函式放進參數中
```javascript=
const giveAccessTo = (name) =>
'Access Granted to ' + name
function authenticate(verify) {
let array = []
for (let i = 0; i < verify; i++) {
array.push(i)
}
return true
}
function setUserVerify(level = guest) {
const range = {
admin: 5,
user: 10,
guest: 20
}
return range[level]
}
function letPerson(person, fn) {
const checkIfSuccess = fn(setUserVerify(person.level))
if(checkIfSuccess) return giveAccessTo(person.name)
else console.log('fail to vertify')
}
letPerson({level: 'user', name: 'Tim'}, authenticate)
```
- 使用 arrow function 讓程式碼看起來更簡潔
```javascript=
const mutiplyBy = (num1) => (num2) => num1*num2
// =
const mutiplyBy = function(num1) {
return function(num2) {
num1 * num2
}
}
```
### 五、Closures

**closure 是函式與聲明他的詞法環境的結合**
閉包允許函式從封閉的範圍或環境訪問變數,即使他離開了宣告他的範圍
- closure 可以節省內存
```javascript=
// 在這個函式,每執行一次都會創造出一個 bigArray
function heavyDuty(item) {
const bigArray = new Array(7000).fill('😄')
console.log('created!');
return bigArray[item]
}
heavyDuty(699)
heavyDuty(699)
heavyDuty(699)
// 如果使用closure,就能只創造一個 bigArray 並復用
function heavyDuty2() {
const bigArray = new Array(7000).fill('😄')
console.log('created Again!')
return function(item) {
return bigArray[item]
}
}
const getHeavyDuty = heavyDuty2();
getHeavyDuty(699)
getHeavyDuty(699)
getHeavyDuty(699)
```
### 六、Closures and Memory

closure 是在暫存堆內做執行,而不是在 call stack 內
```javascript=
function boo(string) {
return function(name) {
return function(name2) {
console.log(`hi ${name2}`)
}
}
}
// =
const boo2 = (string) => (name) => (name2) => console.log(`hi ${name2}`)
```
### 七、Closures and Encapsulation
- Encapsulation 封裝
透過 closure,外界不必看到或可能被操縱的訊息則封裝在函式內
需要使用的屬性和方法則可以取用
```javascript=
const makeNuckearButton = () => {
let timeWithoutDestruction = 0
const passTime = () => timeWithoutDestruction++
const totalPeaceTime = () => timeWithoutDestruction;
const lauch = () => {
timeWithoutDestruction = -1
return 'Boom'
}
setInterval(passTime, 1000)
return {
totalPeaceTime: totalPeaceTime
}
}
const ohno = makeNuckearButton()
ohno.totalPeaceTime()
```
- example
```javascript=
function a() {
let grandpa = 'grandpa'
return function b() {
let father = 'father'
return function c() {
let son = 'son'
return `${grandpa} > ${father} > ${son}`
}
}
}
```
在 c function 裡面有用到的變數,js就會認為你有用到它,並把它放進 closure
js 會保存任何有在子函式用到的變數 ⇢ closure
### 八、Solution: Closures
- 範例:只初始化一次 view
```javascript=
let view
function initialize() {
let called = 0
return function() {
if(called > 0) {
return
} else {
view: 'view'
called++
console.log('view has been set')
}
}
}
const startOnce = initialize()
startOnce()
```
- 範例:settimeout for 迴圈
因為使用 var 宣告 i,i 是存在 global 環境中,沒有 function 把它包起來,再經過 3 秒過後,for 迴圈已經跑完了,這時 settimeout 裡面拿到的 i 就是跑完的值 4
```javascript=
const array = [1,2,3,4]
for(var i = 0; i < array.length; i++) {
setTimeout(() => {
console.log('I am at index' + array[i])
}, 3000)
}
// 4
// 4
// 4
// 4
```
使用 let 形成 block scope: 改用 let 宣告 i,因為 let 會形成 block scope,為每一個 i 創造一個範圍,把 i 保存在 block scope 內
```javascript=
const array = [1,2,3,4]
for(let i = 0; i < array.length; i++) {
setTimeout(() => {
console.log('I am at index' + array[i])
}, 3000)
}
// 1
// 2
// 3
// 4
```
使用 function 閉包形成 function scope:
形成 function scope 後就能把 i 保存在 scope 中
```javascript=
const array = [1,2,3,4]
for(var i = 0; i < array.length; i++) {
(function(index) {
setTimeout(() => {
console.log('I am at index ' + array[index])
}, 3000)
})(i)
}
// 1
// 2
// 3
// 4
```
> 單純只用 var 沒有形成 scope,每次執行拿到的 i 都是 for 迴圈執行完後的值。
> 有使用 function scope 或
### 九、Closures Review
### 十、Prototypal Inheritance

Array 和 function 的原型繼承都是 object
#### 1. 建構函數
透過__proto__可以沿著原型鍊往上找原型繼承
```javascript=
const array1 = []
const arrayConstructor = array1.__proto__
// arrayConstructor 為 array1 的建構函數
```

Array 的建構函數用來創造 Array 的變數
從 Array 的建構函數在往上找,可以找到 object 的建構函數
```javascript=
const array1 = []
const objectConstructor = array1.__proto_.__proto__
// objectConstructor 為 object 的建構函數
```
#### 2. 繼承是一個對象可訪問另一個對象的屬性與方法
array 變數透過原型鍊可以使用 object 的方法 toLocalString()
```javascript=
const array = []
array.toLocaleString() // ''
```
#### 3. 使用 prototype 有什麼好處?
object 可以共享 prototype,可以讓 object 指向內存的同一個位置,進而提高效率
#### 4. function 的 prototype
- 一個 function 變數通常有這些屬性,但事實上 call(),apply(),bind() 不是存在 proterties 裡面

- multiplyBy5 得 call(),apply(),bind() 是存在他的建構函式 Function 的 Prototype 裡面

```javascript=
function multiplyBy5(num) {
return num*5
}
```
- 按照上圖,multiplyBy5 的 __proto__ 會連結到 Function 建構函數的 prototype,這兩個物件會是相同的
```javascript=
multiplyBy5.__proto__ === Function.prototype
// true
```
- Function 的 __proto__ 會連接到 Object 的 prototype
```javascript=
multiplyBy5.__proto__.__proto__ === Object.prototype
```
- 如何使用 prototype
- 不要使用 __proto__ 來訪問 prototype,在 JS 中會有效能上的問題
- 使用 Object.create() 來使用 prototype
```javascript=
let human = {
mortal: true
}
let socrates = Object.create(human)
console.log(human.isPrototypeOf(socrates)) // true
console.log(socrates.mortal) // true
```
- 只有 function 有 prototype
```javascript=
typeof Object // function
typeof Object.prototype // object
```
- Excercise 1
下面的 lastYear 方法不存在 Date 的 prototype 裡面,要如何把它加進去呢
```javascript=
new Date('1900-10-10').lastYear()
```
```javascript=
Date.prototype.lastYear = function() {
return this.getFullYear() - 1
}
```
- Excercise 2
Mofify .map() to print '🗺' at the end of each item.
```javascript=
console.log([1,2,3].map())
//1🗺, 2🗺, 3🗺
```
```javascript=
Array.prototype.map = function() {
let array = []
for(let i = 0; i < this.length; i++) {
array.push(this[i]+'🥌')
}
return array
}
```
- Exercise 3
How would you be able to create your own .bind() method using call or apply.
```javascript=
Function.prototype.bind = function(){
}
```
```javascript=
Function.prototype.bind2 = function(whoIsCallingMe, ...argu){
const self = this;
return function(){
return self.apply(whoIsCallingMe, ...argu);
};
}
```
### 十一、Solution: Prototypal Inheritance
### 十二、Imposter Syndrome
去教別人你所知道的知識,解決你的冒牌者症候群
Object Oriented Programming
---
### 一、OOP and FP
使用 OOP 和 FP 對程式碼的好處

### 二、OOP Introduction
### 三、OOP1: Factory Functions
試想,如果要創造出多個屬性及方法相似的物件要怎麼做
```javascript=
const elf = {
name: 'Orewll',
weapon: 'bow',
attack() {
return 'attack with ' + this.weapon
}
}
const elf = {
name: 'Joe
',
weapon: 'bow',
attack() {
return 'attack with ' + this.weapon
}
}
```
使用**工廠函式**
```javascript=
const creatElf = (name, weapon) => {
return {
name,
weapon,
attack() {
return `Attack with ${this.weapon}`
}
}
}
const elfPeter = creatElf('Peter', 'bow')
const elfTom = creatElf('Tom', 'Arrow')
```
雖然工廠模式可以方便地創造出物件,但每一個物件都會多創造一個 attack 函式,但每個attack 函式都是一模一樣的,這樣會多占了記憶體位置,有沒有節省記憶體的方法呢?
### 四、OOP2: Object.create()
使用物件繼承的特性,繼承 attact 函式,這樣 attck 函式就不會重複佔用記憶體。需要使用 attck 時,只需透過原型鍊調用
```javascript=
const elfFunction = {
attack() {
return 'Attack with ' + this.weapon
}
}
function creatElf(name, weapon) {
let newElf = Object.create(elfFunction)
newElf.name = name
newElf.weapon = weapon
return newElf
}
const elfPeter = creatElf('Peter', 'bow')
const elfTom = creatElf('Tom', 'Arrow')
elfPeter.attack()
```
### 五、OOP3: Constructor Functions
- Constructor Functions 命名需要使用大寫
- 對建構函式使用 new 會回傳 object
- new 會改變 this 的指向給回傳的 object
- 因為建構函式是函式,所以可以使用 prototype 來新增方法
```javascript=
function Elf(name, weapon) {
this.name = name
this.weapon =weapon
}
Elf.prototype.attack = function() {
return 'attack with ' + this.weapon
}
// 一般函式是 dynamically scope,this 的指向指到呼叫他的 object
// Elf.prototype.attack = () => {
// return 'attack with ' + this.weapon
// }
// 箭頭函式是 lexically scope,this 的指向會指目前 function 的環境 => global
const peter = new Elf('Peter', 'stones') // this 指向 peter
console.log(peter.attack())
```
### 六、More Constructor Functions
### 七、Funny Thing About JS...
技術上來說,除了 null 和 undefined,每個東西都是 object
```javascript=
var a = new Number(5)
typeof a // object
var b = 5
typeof b // number
a === b // false
b.toSring()
// b 是 number,為什麼可以使用 toString() 的方法?
```
當我們使用 var b = 5,在記憶體內分配變數時,他會建構數字,Javascipt 看到你想要使用對象方法,因此他會自動假定你的意思是 object 而不是 primitive
### 八、OOP4: ES6 Classes
- 透過 ES6 的 class 語法糖,可以把建構函式的屬性和方法都包在一起
- 方法不寫在 constructor 裡面,因為方法的程式碼是固定的,如果寫進去 constructor,在每次用 new 實體化一個 class 時,都會多創造一個方法,多佔了記憶體的空間
```javascript=
class Elf {
constructor(name, weapon) {
this.name = name
this.weapon = weapon
}
attack() {
return 'attack with ' + this.weapon
}
}
cont peter = new Elf('Peter', 'stones')
```
### 九、Object.create() vs Class
### 十、this - 4 Ways
- new binding this
使用在建構函式上
```javascript=
function Person() {
this.name = name
this.age = age
}
const person1 = new Person('Mary', 20)
```
- implicit binding 隱性綁定
不做任何事情,this 自動綁定
```javascript=
const person2 = {
name: 'Karen',
age: 34,
hi() {
console.log('hi ' + this.name) // this 自動綁定到 name 上
}
}
person.hi()
```
- explicit binding 顯性綁定
用 bind, apply, call 指定 this 要綁定到哪裡
```javascript=
const person3 = {
name: 'Karen',
age: 34,
hi: function() {
console.log('hi ' + this.setTimeout)
}.bind(window)
}
person3.hi()
```
- arrow function
箭頭函式的 this 指向詞彙環境,與一般函式是看誰呼叫它的動態 scope 不同
```javascript=
const person4 = {
name: 'Karen',
age: 34,
hi: function() {
var inner = () => {
console.log('hi ' + this.name)
}
return inner()
}
}
person4.hi()
```
### 十一、Inheritance
- 使用 extends 來繼承 prototype
- 在 child class 要使用 this,得先呼叫 super()
```javascript=
class Character {
constructor(name, weapon) {
this.name = name
this.weapon = weapon
}
attack() {
return 'Attack with ' + this.weapon
}
}
class Elf extends Character {
constructor(name, weapon, type) {
super(name, weapon)
this.type = type
}
}
const Dolby = new Elf('Dolby', 'clothes', 'house')
console.log(Dolby)
```
### 十二、ES2020: Private Class Variables
### 十三、Public vs Private
使用 # 來宣告私有變數
```javascript=
class Character {
#age = 54
constructor(name, weapon) {
this.name = name
this.weapon = weapon
}
attack() {
return 'Attack with ' + this.#age
}
}
class Elf extends Character {
constructor(name, weapon, type) {
super(name, weapon)
this.type = type
}
}
const Dolby = new Elf('Dolby', 'clothes', 'house')
Dolby.attack() // 'Attack with 54'
Dolby.age // Private field '#age' must be declared in an enclosing class
```
### 十四、OOP in React.js
### 十五、4 Pillars of OOP
### 十六、OOP and Polymorphism
Functional Programming
---
### 一、 Functional Programming Introduction
### 二、Exercise: Amazon
### 三、Pure Functions
#### 容易測試
#### 容易組合
#### No Side Effect
- 以下的程式碼會產生 side effects
下面的 function 都會更改 array 裡面的值
```javascript=
//Side effects:
const array = [1,2,3];
function mutateArray(arr) {
arr.pop()
}
function mutateArray2(arr) {
arr.forEach(item => arr.push(1
))
}
//The order of the function calls will matter.
mutateArray(array)
mutateArray2(array)
array
```
- 以下的程式碼修改為不會產生 side effects
```javascript=
//Side effects:
const array = [1,2,3];
function removeLastItem(arr) {
const newArray = [].concat(arr)
newArray.pop()
return newArray
}
function mutiplyBy2(arr) {
return arr.map(item => item*2)
}
//The order of the function calls will matter.
const array2 = removeLastItem(array)
const array3 = mutiplyBy2(array)
console.log(array)
```
#### Same input ⇢ Same Output
同樣的 input slice 不管呼叫幾次,結果都是一樣的 ⇢ Pure Function
但 splice 儘管 input 一樣,但每次呼叫的值都不同 ⇢ Impure Function
```javascript=
var xs = [1, 2, 3, 4, 5];
// pure(純)
xs.slice(0, 3);
//=> [1, 2, 3]
xs.slice(0, 3);
//=> [1, 2, 3]
xs.slice(0, 3);
//=> [1, 2, 3]
// impure(不純)
xs.splice(0, 3);
//=> [1, 2, 3]
xs.splice(0, 3);
//=> [4, 5]
xs.splice(0, 3);
//=> []
```
### 四、Can Everything Be Pure?
Pure Function 的目標不是要讓所有程式碼變得沒有 side effects。
而是以某種方式組織程式碼,以便隔離 side effects
當發生錯誤時,我們能迅速排除 pure function(因為他們是 pure),找到可能會造成 side effect 的程式碼

- 只做一項任務
- 每個函式都要有 return
- Pure
- 沒有和其他程式碼分享的 state
- 不可修改 global state
- 可以組合的
- 可預測的
### 五、Idempotent
Idempotent 使程式碼是可預測的
- 儘管初始值被改了(impure),但不論呼叫多少次這個 function,回應的值都是一樣的
```javascript=
Math.abs(Math.abs(-50)) // 50
function sayHi() {
console.log('hi')
}
sayHi()
sayHi()
sayHi()
```
### 六、Imperative vs Declarative
### 七、Immutability
不改變數據,也不改變狀態
需要數據時,拷貝一份回傳新狀態
```javascript=
const obj = {name: 'Andrei'}
function clone(obj) {
return {...obj}
}
function updateName(obj) {
const obj2 = clone(obj)
obj2.name = 'Nana'
return obj2
}
const updatedObj = updateName(obj)
console.log(obj, updatedObj)
```
### 八、Higher Order Functions and Closures
```javascript=
const closure =function() {
let count = 0
return function increment() {
count++
return count
}
}
const incrementFn = closure()
incrementFn()
```
- 使用 closure 可以建立私有變數
```javascript=
const closure =function() {
let count = 55
return function getCount() {
return count
}
}
const getCounter = closure()
getCounter()
```
### 九、Currying
### 十、Partial Application
使用 bind 來傳遞參數
```javascript=
const mutiply = (a, b, c) => a * b * c
const partialMultiplyBy5 = mutiply.bind(null, 5)
partialMultiplyBy5(4, 10) // 200
const partialMultiplyBy20 = mutiply.bind(null, 5, 4)
partialMultiplyBy20(10) // 200
```
### 十一、MCI: Memoization 1
使用物件的 key-value 屬性去做 cache,避免 function 重複執行
```javascript=
let cache = {}
function memoizedAddTo80(n) {
if(n in cache) {
return cache[n]
} else {
console.log('long time')
cache[n] = n + 80
return cache[n]
}
}
console.log('1', memoizedAddTo80(5))
console.log('1', memoizedAddTo80(5))
// long time
// 11 1 85
// 12 1 85
```
### 十二、MCI: Memoization 2: use curry
避免污染到全域變數,將 cache data 做成 curry
```javascript=
function memoizedAddTo80() {
let cache = {}
return function(n) {
if(n in cache) {
return cache[n]
} else {
console.log('long time')
cache[n] = n + 80
return cache[n]
}
}
}
const curry = memoizedAddTo80()
console.log('1', curry(5))
console.log('1', curry(5))
```
### 十三、Compose and Pipe
compose 像是工廠的傳輸帶的組合設計原則
data ⇢ fn ⇢ data ⇢ fn ⇢ data
- compose fn 可以組合函式,同時執行多個函式的功能,執行方向由右而左
- pipe fn 的功能相同,但執行方向由左而右
```javascript=
const compose = (f, g) => (data) => f(g(data))
const compose = (f, g) => (data) => g(f(data))
const mutiplyBy3 = (num) => num * 3
const makePositive = (num) => Math.abs(num)
const mutiplyBy3AndAbsolyte = compose(multiplyBy3, makePositive)
mutiplyBy3AndAbsolyte(-50)
```
- compose & pipe 的另一種寫法
```javascript=
const multiply20 = (price) => price * 20;
const divide100 = (price) => price / 100;
const normalizePrice = (price) => price.toFixed(2);
const addPrefix = (price) => "$" + String(price);
const pipe =
(...fns) =>
(x) =>
fns.reduce((res, fn) => fn(res), x);
const compose =
(...fns) =>
(x) =>
fns.reduceRight((res, fn) => fn(res), x);
const discountPipe = pipe(multiply20, divide100, normalizePrice, addPrefix);
const discountCompose = compose(
addPrefix,
normalizePrice,
divide100,
multiply20
);
discountPipe(200); // '$40.00'
discountCompose(200); // '$40.00'
```
### 十四、Arity
fn 帶的參數最好不要超過兩個,超過兩個參數的 fn 很難做組合
### 十五、Is FP The Answer To Everything?
### 十六、Solution: Amazon
```javascript=
const user = {
name: 'Kim',
active: true,
cart: [],
purchases: []
}
const history1 = [];
const compose = (f, g) => (...args) => f(g(...args))
const pipe = (f, g) => (...args) => g(f(...args))
const purchaseItem = (...fns) => fns.reduce(compose);
const purchaseItem2 = (...fns) => fns.reduce(pipe);
purchaseItem2(
addItemToCart,
applyTaxToItems,
buyItem,
emptyUserCart,
)(user, {name: 'laptop', price: 60})
// purchaseItem(
// emptyUserCart,
// buyItem,
// applyTaxToItems,
// addItemToCart
// )(user, {name: 'laptop', price: 50})
function addItemToCart(user, item) {
history1.push(user)
const updatedCart = user.cart.concat(item)
return Object.assign({}, user, {cart: updatedCart});
}
function applyTaxToItems(user) {
history1.push(user)
const {cart} = user;
const taxRate = 1.3;
const updatedCart = cart.map(item => {
return {
name: item.name,
price: item.price*taxRate
}
})
return Object.assign({}, user, { cart: updatedCart });
}
function buyItem(user) {
history1.push(user)
const itemsInCart = user.cart;
return Object.assign({}, user, { purchases: itemsInCart });
}
function emptyUserCart(user) {
history1.push(user)
return Object.assign({}, user, { cart: [] });
}
function refundItem() {
}
function getUserState() {
}
function goBack() {
}
function goForward() {
}
```
OOP vs FP
---
### 一、Composition vs Inheritance
使用 Composition 模擬出 class 方法
```javascript=
function getAttack(character) {
return Object.assign({}, character, {attackFn: () => {console.log('attack')}})
}
function getSleep(character) {
return Object.assign({}, character, {sleepFn: () => {console.log('sleep')}})
}
function Elf(name, weapon, type){
let elf = {
name,
weapon,
type
}
return getAttack(getSleep(elf))
}
```
### 二、OOP vs FP
Asynchronous JavaScript
---
### 一、How JavaScript Works
```javascript=
setTimeout(()=>{console.log('0', 'is the loneliest number')}, 0)
setTimeout(()=>{console.log('1', 'can be as bad as one')}, 10)
//2
Promise.resolve('hi').then((data)=> console.log('2', data))
//3
console.log('3','is a crowd')
// 3 沒有 web api 也非異步
// 2 異步
// 0 web apicloud latitude
// 1 web api
```
### 二、Promises
Promise 是一個 Object,他在未來產生的三個狀態,resolve、reject、pending
- Promise 的出現是為解決 callback hall
### 1. Promise 起手式
```javascript=
const promise = new Promise((resolve, reject) => {
if(true) {
resolve('Stuff Work')
} else {
reject('Error')
}
})
promise.then(result => console.log(result))
```
### 三、ES8 - Async Await
### 四、ES9 (ES2018)
### 五、ES9 (ES2018) - Async
### 六、Job Queue
### 七、Parallel, Sequence and Race
### 八、ES2020: allSettled()
### 九、ES2021: any()
### 十、Threads, Concurrency and Parallelism
Modules In JavaScript
---
### 一、 What Is A Module?
### 二、Module Pattern
- Global Scope
- Module Scope
- Function Scope
- Block Scope - let & const
- Module 可以在不同函式間分享變數,而不污染到全域變數
- 使用 IIEF 和 closure 做成 Module
- 使用 IIFE 執行函式,不會污染到全域變數
```javascript=
(function(){
var harry = 'potter'
var voldemort = 'He who must not be named'
function fight(char1, char2) {
var attack1 = Math.floor(Math.random() * char1.length);
var attack2 = Math.floor(Math.random() * char2.length);
return attack1 > attack2 ? `${char1} wins` : `${char2} wins`
}
console.log(fight(harry, voldemort))
})()
```
- module pattern 回傳出 public api,大家都可以使用
```javascript=
const flightModule = (function(){
var harry = 'potter'
var voldemort = 'He who must not be named'
function fight(char1, char2) {
var attack1 = Math.floor(Math.random() * char1.length);
var attack2 = Math.floor(Math.random() * char2.length);
return attack1 > attack2 ? `${char1} wins` : `${char2} wins`
}
return {
fight
}
})()
flightModule.fight('a','anc')
```
- module pattern 可以取用 global 變數,但是不更改變數
```javascript=
var globalSecret = '12345'
var script = (function(globalSecret){
globalSecret = 0
console.log(globalSecret) // 0
})(globalSecret)
console.log(globalSecret) // 12345
```
### 三、Module Pattern Pros and Cons
Module Pattern 仍然有兩個缺點
- 仍然污染到全域變數,雖然污染的變數變少了
- 命名的名稱可能會衝突
```javascript=
var script = (function(globalSecret){
globalSecret = 0
console.log(globalSecret) // 0
})(globalSecret)
// other script
var script = 'hahaha, I replace you'
```
### 四、CommonJS, AMD, UMD
- CommonJS 是同步的模組,通常使用在 server 端,像是:node.js
- 因為他是同步的,我們會使用 webpack 或 browserify 將所有的模組捆成一包,他們會知道模組的先後順序,誰要引用誰。
- AMD 是非同步的模組,使用在瀏覽端
- require.js 就是使用 AMD 模組
- UMD 同時使用 CommonJS 和 AMD
### 五、ES6 Modules
- 使用 import 來輸入 module,[link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
- 使用 export 來輸出 module,[link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export)
- script 需要加入 module 的 type
Error Handling
---
### 一、Errors In JavaScript
- 當使用 throw 時。目前運行的腳本就會停止
- Error 有三個物件可以使用,name, message, stack
- 透過 stack 我們可以知道,程式碼哪裡出錯
```javascript=
const a = () => {
throw new Error('Opps')
}
a()
/* VM5057:2 Uncaught Error: Opps
at a (<anonymous>:2:11)
at <anonymous>:1:1
a @ VM5057:2
(anonymous) @ VM5071:1 */
```
### 二、Try Catch
```javascript=
function error() {
try {
// 嘗試執行程式碼
throw new Error('err~~')
} catch(error) {
// error 參數則會接收 tyr 拋出的錯誤
// 如果有錯誤則在這邊執行
console.log(error)
/*
Error: err~~
at <anonymous>:3:11*/
} finally {
// 不管有沒有錯誤都會執行
console.log('finally')
}
console.log('run me')
}
error()
```
- try catch 只能用在同步,非同步的程式碼要另外處理
### 三、 Async Error Handling
非同步的錯誤捕捉
- Promise catch(),確保每個 Promise 務必要有 catch
```javascript=
Promise.resolve('asyncfail')
.then(response => {
console.log(response)
throw new Error('#1 fail')
})
.then(response => {
console.log(response)
})
.catch(err => {
console.error('error', err.message)
})
.then(response => {
console.log('hi am I still needed?', response)
return 'done'
})
.catch(err => {
console.error(err)
return 'failed'
})
```
- async await + try catch,Promise 沒使用 catch() 捕捉錯誤時,可以加上 try catch 捕捉錯誤
```javascript=
(async function() {
try {
await Promise.reject('oopsie')
} catch (err) {
console.error(err)
}
console.log('This is still good!')
})()
```
### 四、 Extending Errors
```javascript=
class authenticationError extends Error {
constructor(message) {
super(message)
this.name = 'ValidationError'
this.message = message
}
}
class PermissionError extends Error {
constructor(message) {
super(message)
this.name = 'PermissionError'
this.message = message
this.favouriteSnack = 'grapes'
}
}
class DatabaseError extends Error {
constructor(message) {
super(message)
this.name = 'DatabaseError'
this.message = message
}
}
throw new PermissionError('A permission error')
```
Data Structures In JavaScript
---
### 一、What Is A Data Structure?
### 二、How Computers Store Data
### 三、Data Structures In Different Languages
### 四、Operations On Data Structures
### 五、Array Introduction
### 六、Static vs Dynamic Arrays
### 七、Implementing An Array
### 八、Strings and Arrays
### 九、Solution Reverse A String
### 十、Solution: Merge Sorted
### 十一、Arrays Review
### 十二、Hash Tables Introduction
### 十三、Hash Function
### 十四、Hash Collisions
### 十五、Hash Tables In Different Languages
### 十六、Solution: Implement A Hash Table
### 十六、keys()
### 十七、Hash Tables vs Arrays
### 十八、Solution: First Recurring Character
### 十九、Hash Tables Review
歷代 ES fn Introduce
---
### 一、ES7
- includes
```javascript=
const pets = ['cat', 'dog', 'bat']
pets.includes('dog') // true
'apple'.includes('a') // true
```
- ** 平方
```javascript=
const squre = (x) => x**2
squre(20) // 400
```
### 二、ES8
- padStart && padEnd 固定數目在字串前留空 & 在字串後留空
```javascript=
'apple'.padStart(10) // ' apple'
'a'.padStart(10) // ' a'
'banana'.padEnd(10) // 'banana '
```
- Object.keys & Object.values & Object.entries
```javascript=
let obj = {
username0: 'Santa',
username1: 'Rudolf',
username2: 'Mr. Grinch',
}
Object.keys(obj).foreach((key, index) => {
console.log(key, obj[key])
})
// username0 Santa
// username1 Rudolf
// username2 Mr. Grinch
Object.values(obj).forEach(value => {
console.log(value)
})
// Santa
// Rudolf
// Mr. Grinch
Object.entries(obj).forEach(value => {
console.log(value)
})
/*
[
"username0",
"Santa"
]
[
"username1",
"Rudolf"
]
[
"username2",
"Mr. Grinch"
]
*/
```
### 三、ES9
### 四、ES10
- flat & flatmap 將陣列攤平
```javascript=
const array = [[1,2],[[[1]]],30]
array.flat(5) // [1, 2, 1, 30]
const arrayAdd = array.flatMap(item => item + '🖊')
// ['1,2🖊', '1🖊', '30🖊']
```
- trimStart & trimEnd 去除前後空白
```javascript=
const userEmail = ' mail@gmail.com '
console.log(userEmail.trimStart())
// ' mail@gmail.com'
console.log(userEmail.trimEnd())
// 'mail@gmail.com '
```
- fromEntries & entries
```javascript=
userProfiles = [['Tom', 20], ['John', 25], ['Kim', 31]]
const peopleObject = Object.fromEntries(userProfiles)
/* {
"Tom": 20,
"John": 25,
"Kim": 31
}*/
const peopleArray = Object.entries(peopleObject)
/*
[
[
"Tom",
20
],
[
"John",
25
],
[
"Kim",
31
]
]
*/
```
### 五、Advanced Loops
- for of 使用在可枚舉的"陣列"及"字串"
```javascript=
const basket = ['apple', 'oranges', 'grapes']
for(item of basket) {
console.log(item)
}
// apple
// oranges
// grapes
```
- for in 使用在可枚舉的"物件"
類似於 Object.keys
```javascript=
const detailBasket = {
apples: 5,
oranges: 10,
grapes: 100
}
for(item in detailBasket) {
console.log(item)
}
// apple
// oranges
// grapes
```
### 六、ES2020
- BigInt
在超過最大安全數字(九千兆)時,在數字後面加 n,代表他是 bigint,這樣做算出才不會有問題
```javascript=
Number.MAX_SAFE_INTEGER // 9007199254740991
9007199254740991 + 10
// 9007199254741000
// 會出現錯誤
9007199254740991n + 10n
// 9007199254741001n
// 正確
typeof 10n // 'bigint'
```
- Option Chaining Operator ?.
用來確認是否物件內的 key 值是否存在,存在才拿取,不存在則回應 undefined
```javascript=
let will_pokemon = {
pikachu: {
species: 'Mouse',
height: 0.4,
weight: 6
}
}
let andrei_pokemon = {
raichu: {
species: 'Mouse',
height: 0.8,
weight: 30
}
}
let weight = andrei_pokemon ?. pikachu ?. weight // undefined
let weight2 = will_pokemon ?. pikachu ?. weight // 6
```
- Nulllish Coalescing Operator ??
用來取代 || (or),因為我們常常會判斷 0, '', false 在物件中的值為不存在
```javascript=
let will_pokemon = {
pikachu: {
species: 'Mouse',
height: 0.4,
weight: 6,
power: 0
}
}
let power = will_pokemon ?. pikachu ?. power ?? 'no power'
console.log(power) // 0
let power2 = will_pokemon ?. pikachu ?. power || 'no power'
console.log(power2) // no power
```
- globalThis
為了統一在不同系統 node 和瀏覽器中使用的變數,
但 globalThis 在 node 中就等於 global
globalThis 在瀏覽器中就等於 this
### 七、ES2021
- replaceAll
原本的 replace 功能只會替換第一個找到的字串,但 replaceAll 會替換全部
```javascript=
cosnt str = 'ztm is the best of best'
const replaceAll = str.replaceAll('best', 'worst')
// 'ztm is the worst of worst'
const replace = str.replace('best', 'worst')
// 'ztm is the worst of best'
```
### 八、 debugger
使用 debugger 來終止程式碼
```javascript=
const flat = [[0,1],[2,3],[4,5]].reduce(
(acc, current) => {
debugger
return acc.concat(current)
}
)
```
### 九、
### 十、
### 十一、
### 十二、
### 十三、
### 十四、
### 十五、
### 十六、
---
### 一、
### 二、
### 三、
### 四、
### 五、
### 六、
### 七、
### 八、
### 九、
### 十、
### 十一、
### 十二、
### 十三、
### 十四、
### 十五、
### 十六、
---
### 一、
### 二、
### 三、
### 四、
### 五、
### 六、
### 七、
### 八、
### 九、
### 十、
### 十一、
### 十二、
### 十三、
### 十四、
### 十五、
### 十六、
---
### 一、
### 二、
### 三、
### 四、
### 五、
### 六、
### 七、
### 八、
### 九、
### 十、
### 十一、
### 十二、
### 十三、
### 十四、
### 十五、
### 十六、
---
### 一、
### 二、
### 三、
### 四、
### 五、
### 六、
### 七、
### 八、
### 九、
### 十、
### 十一、
### 十二、
### 十三、
### 十四、
### 十五、
### 十六、