Try   HackMD

Advanced Javascript

Table of Contents

tags: Javascript

Javascript Foundation

一、Call Stack && Memory Heap(內存堆)

1. Call Stack 與 Stack Overflow

// 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這個變數就會被垃圾回收掉

function subtraction(num) { var human = { first: 'Andrei', last: 'Neagoie' } return 2 }

3. Memory Heap 與 Memory leak

  • 用越來越多的數據去填充內存堆,垃圾搜集機制不會起作用,因為 for 迴圈一直執行它。
let array = [] for (let i = 5; i > 1; i++) { array.push(i-1) }

4. 常見的記憶體洩漏

  • Global variable
    使用越多全域變數,會佔用越多記憶體內存

  • event listener
    如果你一直增加監聽器而不刪除他,特別是在 SPA 網頁用戶來回移動時,會一直創造新的監聽器。內存就會增加越來越多的事件監聽器

  • setInterval
    setInterval 除非我們清除他,不然他都不會被垃圾回收

二、Execution Context

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 前,我就可以讀取這個變數及函式

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?
    • 變數使用 letconst 宣告
    • 函式使用 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
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
function a(am,bm) { console.log(Array.from(arguments)) } function a(...args) { console.log(args) }

七、Variable Environment

  • execution context 會自成一個自己的變數環境,不受其他 execution context 影響
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

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 內的變數就無法讓外部訪問
function a() { var secret = '12345' } console.log(secret) // Uncaught ReferenceError
  • Block scope 指的是除了 function 外,其他的 { } 範圍,但這僅限於 ES6 後使用 let, const 來說。因為如果是使用 var,在 { } 外仍然可以訪問到 { } 內的變數 ⇢ 建議大部分都用 let 和 const 來築成 block scope
if(5>4) { var secret = '12345' } console.log(secret) // 12345 if(5>4) { var secret2 = '12345' } console.log(secret2) //Uncaught ReferenceError
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 參照的對象主要是看哪一個物件呼叫他
const obj = { name: 'Bitty', other: { name: 'Abby', singA() { return 'lalala ' + this.name } }, } obj.other.singA() // lalala Abby
  • this 讓方法可以讀取物件
const obj = { name: 'Billy', sing() { return 'lalala ' + this.name }, singAgain() { return this.sing() + '!' } } obj.sing()
  • 不同物件可以執行同樣的程式碼
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

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 的物件上
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 上
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 裡面
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 練習

箭頭函式只看詞彙環境 ⇢ 尖頭個性比較直,只會看當下的環境做事
一般函式看誰呼叫它 ⇢ 一般人,位高的人叫他做事他就做

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() 則回返回一個函式
function a() { console.log('hi') } a.call() // hi a.apply() // hi a.bind() // function a() {...}

a() 現在習慣的函式呼叫,算是一種簡寫

  • 使用 call 將 this 成其他物件, ex: 魔法師治療自己,魔法師也治療弓箭手
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() 則需要在對象物件後面寫成陣列
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() 則會在綁定物件及參數後返回一個函式
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 重用一段程式碼,給他部分參數,並創建可擴展的函數

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

const a = [1,2,3] const b = [].concat(a)

2. 物件拷貝

  • shollow copy 淺拷貝
const a = { a: 'a', b: 'b', c: 'c', } const b = Object.assign({}, a) const c = {...a}
  • deep copy 深拷貝
    但要注意,使用 JSON 深拷貝的方法有效能上的問題
const a = { a: 'a', b: 'b', c: { say: 'cccc' } } const b = JSON.parse(JSON.stringify(a))
  • 如何比較來自不同位址的物件,但物件property是相同的
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
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

  • 函式可以分配給變數或屬性
var vari = function var obj = { fn: function() {} }
  • 函式可以作為參數傳遞給函式
function a(fn) { fn() } a(function() {console.log('Hello World')})
  • 函式可以作為其他函數的值返回
function b() { return function c() {console.log('bye')} } var d = b() d()

三、Extra Bits: Functions

在 function 的參數裡加上預設值,以免參數出現 undefined 的狀況

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 原則?
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 將函式放進參數中
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 讓程式碼看起來更簡潔
const mutiplyBy = (num1) => (num2) => num1*num2 // = const mutiplyBy = function(num1) { return function(num2) { num1 * num2 } }

五、Closures


closure 是函式與聲明他的詞法環境的結合
閉包允許函式從封閉的範圍或環境訪問變數,即使他離開了宣告他的範圍

  • closure 可以節省內存
// 在這個函式,每執行一次都會創造出一個 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 內

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,外界不必看到或可能被操縱的訊息則封裝在函式內
    需要使用的屬性和方法則可以取用
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
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
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
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 內

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 中

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__可以沿著原型鍊往上找原型繼承

const array1 = [] const arrayConstructor = array1.__proto__ // arrayConstructor 為 array1 的建構函數


Array 的建構函數用來創造 Array 的變數

從 Array 的建構函數在往上找,可以找到 object 的建構函數

const array1 = [] const objectConstructor = array1.__proto_.__proto__ // objectConstructor 為 object 的建構函數

2. 繼承是一個對象可訪問另一個對象的屬性與方法

array 變數透過原型鍊可以使用 object 的方法 toLocalString()

const array = [] array.toLocaleString() // ''

3. 使用 prototype 有什麼好處?

object 可以共享 prototype,可以讓 object 指向內存的同一個位置,進而提高效率

4. function 的 prototype

  • 一個 function 變數通常有這些屬性,但事實上 call(),apply(),bind() 不是存在 proterties 裡面

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

function multiplyBy5(num) { return num*5 }
  • 按照上圖,multiplyBy5 的 proto 會連結到 Function 建構函數的 prototype,這兩個物件會是相同的
multiplyBy5.__proto__ === Function.prototype // true
  • Function 的 proto 會連接到 Object 的 prototype
multiplyBy5.__proto__.__proto__ === Object.prototype
  • 如何使用 prototype
    • 不要使用 proto 來訪問 prototype,在 JS 中會有效能上的問題
    • 使用 Object.create() 來使用 prototype
let human = { mortal: true } let socrates = Object.create(human) console.log(human.isPrototypeOf(socrates)) // true console.log(socrates.mortal) // true
  • 只有 function 有 prototype
typeof Object // function typeof Object.prototype // object
  • Excercise 1
    下面的 lastYear 方法不存在 Date 的 prototype 裡面,要如何把它加進去呢
new Date('1900-10-10').lastYear()
Date.prototype.lastYear = function() { return this.getFullYear() - 1 }
  • Excercise 2
    Mofify .map() to print '🗺' at the end of each item.
console.log([1,2,3].map()) //1🗺, 2🗺, 3🗺
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.
Function.prototype.bind = function(){ }
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

試想,如果要創造出多個屬性及方法相似的物件要怎麼做

const elf = { name: 'Orewll', weapon: 'bow', attack() { return 'attack with ' + this.weapon } } const elf = { name: 'Joe ', weapon: 'bow', attack() { return 'attack with ' + this.weapon } }

使用工廠函式

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 時,只需透過原型鍊調用

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 來新增方法
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

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 時,都會多創造一個方法,多佔了記憶體的空間
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
    使用在建構函式上
function Person() { this.name = name this.age = age } const person1 = new Person('Mary', 20)
  • implicit binding 隱性綁定
    不做任何事情,this 自動綁定
const person2 = { name: 'Karen', age: 34, hi() { console.log('hi ' + this.name) // this 自動綁定到 name 上 } } person.hi()
  • explicit binding 顯性綁定
    用 bind, apply, call 指定 this 要綁定到哪裡
const person3 = { name: 'Karen', age: 34, hi: function() { console.log('hi ' + this.setTimeout) }.bind(window) } person3.hi()
  • arrow function
    箭頭函式的 this 指向詞彙環境,與一般函式是看誰呼叫它的動態 scope 不同
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()
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

使用 # 來宣告私有變數

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 裡面的值
//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
//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

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,回應的值都是一樣的
Math.abs(Math.abs(-50)) // 50 function sayHi() { console.log('hi') } sayHi() sayHi() sayHi()

六、Imperative vs Declarative

七、Immutability

不改變數據,也不改變狀態
需要數據時,拷貝一份回傳新狀態

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

const closure =function() { let count = 0 return function increment() { count++ return count } } const incrementFn = closure() incrementFn()
  • 使用 closure 可以建立私有變數
const closure =function() { let count = 55 return function getCount() { return count } } const getCounter = closure() getCounter()

九、Currying

十、Partial Application

使用 bind 來傳遞參數

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 重複執行

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

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 的功能相同,但執行方向由左而右
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 的另一種寫法
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

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 方法

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

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 起手式

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 執行函式,不會污染到全域變數

(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,大家都可以使用
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 變數,但是不更改變數
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 仍然有兩個缺點

  • 仍然污染到全域變數,雖然污染的變數變少了
  • 命名的名稱可能會衝突
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
  • 使用 export 來輸出 module,link
  • script 需要加入 module 的 type

Error Handling

一、Errors In JavaScript

  • 當使用 throw 時。目前運行的腳本就會停止
  • Error 有三個物件可以使用,name, message, stack
  • 透過 stack 我們可以知道,程式碼哪裡出錯
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

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
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 捕捉錯誤
(async function() { try { await Promise.reject('oopsie') } catch (err) { console.error(err) } console.log('This is still good!') })()

四、 Extending Errors

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
const pets = ['cat', 'dog', 'bat'] pets.includes('dog') // true 'apple'.includes('a') // true
  • ** 平方
const squre = (x) => x**2 squre(20) // 400

二、ES8

  • padStart && padEnd 固定數目在字串前留空 & 在字串後留空
'apple'.padStart(10) // ' apple' 'a'.padStart(10) // ' a' 'banana'.padEnd(10) // 'banana '
  • Object.keys & Object.values & Object.entries
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 將陣列攤平
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 去除前後空白
const userEmail = ' mail@gmail.com ' console.log(userEmail.trimStart()) // ' mail@gmail.com' console.log(userEmail.trimEnd()) // 'mail@gmail.com '
  • fromEntries & entries
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 使用在可枚舉的"陣列"及"字串"
const basket = ['apple', 'oranges', 'grapes'] for(item of basket) { console.log(item) } // apple // oranges // grapes
  • for in 使用在可枚舉的"物件"
    類似於 Object.keys
const detailBasket = { apples: 5, oranges: 10, grapes: 100 } for(item in detailBasket) { console.log(item) } // apple // oranges // grapes

六、ES2020

  • BigInt
    在超過最大安全數字(九千兆)時,在數字後面加 n,代表他是 bigint,這樣做算出才不會有問題
Number.MAX_SAFE_INTEGER // 9007199254740991 9007199254740991 + 10 // 9007199254741000 // 會出現錯誤 9007199254740991n + 10n // 9007199254741001n // 正確 typeof 10n // 'bigint'
  • Option Chaining Operator ?.
    用來確認是否物件內的 key 值是否存在,存在才拿取,不存在則回應 undefined
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 在物件中的值為不存在
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 會替換全部
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 來終止程式碼

const flat = [[0,1],[2,3],[4,5]].reduce( (acc, current) => { debugger return acc.concat(current) } )

九、

十、

十一、

十二、

十三、

十四、

十五、

十六、


一、

二、

三、

四、

五、

六、

七、

八、

九、

十、

十一、

十二、

十三、

十四、

十五、

十六、


一、

二、

三、

四、

五、

六、

七、

八、

九、

十、

十一、

十二、

十三、

十四、

十五、

十六、


一、

二、

三、

四、

五、

六、

七、

八、

九、

十、

十一、

十二、

十三、

十四、

十五、

十六、


一、

二、

三、

四、

五、

六、

七、

八、

九、

十、

十一、

十二、

十三、

十四、

十五、

十六、