--- 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 就會被垃圾回收機制回收掉 ![](https://i.imgur.com/RgLdA40.png) - 當這個函數被掉用完後,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 ![](https://i.imgur.com/gvh2igG.jpg) - 瀏覽器一開始就會建立一個 Global Execution Content,裡面會有 Global Object 和 this ![](https://i.imgur.com/xDMvAtn.jpg) ### 三、Lexical Environment - Lexical Environment 指的是你在哪裡寫下程式碼 - 可以把 Lexical Environment 想成 Execution Context 創建時生成的星球裡面的東西 - ![](https://i.imgur.com/SUDeQhv.jpg) ### 四、Hoisting ![](https://i.imgur.com/ofCV5CQ.jpg) - 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 ![](https://i.imgur.com/nJLS2Ml.jpg) - argument 並不是一個 array,他並不能使用使用 array 的方法遍歷 ![](https://i.imgur.com/q51u1nJ.png) - 可以使用 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 影響 ![](https://i.imgur.com/ZurFJZh.png) ```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 ![](https://i.imgur.com/qdeMJW5.jpg) ```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 參照的對象主要是看哪一個物件呼叫他 ![](https://i.imgur.com/ErFttZz.png) ```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 ![](https://i.imgur.com/ZZPu3Td.jpg) **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 ![](https://i.imgur.com/DHeUg8y.jpg) 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 ![](https://i.imgur.com/f5fLH8f.jpg) Array 和 function 的原型繼承都是 object #### 1. 建構函數 透過__proto__可以沿著原型鍊往上找原型繼承 ```javascript= const array1 = [] const arrayConstructor = array1.__proto__ // arrayConstructor 為 array1 的建構函數 ``` ![](https://i.imgur.com/vDJLtCY.png) 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 裡面 ![](https://i.imgur.com/jNlDSTR.jpg) - multiplyBy5 得 call(),apply(),bind() 是存在他的建構函式 Function 的 Prototype 裡面 ![](https://i.imgur.com/FGdyRHJ.jpg) ```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 對程式碼的好處 ![](https://i.imgur.com/LfK2azq.jpg) ### 二、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 的程式碼 ![](https://i.imgur.com/JAnhdGc.jpg) - 只做一項任務 - 每個函式都要有 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) } ) ``` ### 九、 ### 十、 ### 十一、 ### 十二、 ### 十三、 ### 十四、 ### 十五、 ### 十六、 --- ### 一、 ### 二、 ### 三、 ### 四、 ### 五、 ### 六、 ### 七、 ### 八、 ### 九、 ### 十、 ### 十一、 ### 十二、 ### 十三、 ### 十四、 ### 十五、 ### 十六、 --- ### 一、 ### 二、 ### 三、 ### 四、 ### 五、 ### 六、 ### 七、 ### 八、 ### 九、 ### 十、 ### 十一、 ### 十二、 ### 十三、 ### 十四、 ### 十五、 ### 十六、 --- ### 一、 ### 二、 ### 三、 ### 四、 ### 五、 ### 六、 ### 七、 ### 八、 ### 九、 ### 十、 ### 十一、 ### 十二、 ### 十三、 ### 十四、 ### 十五、 ### 十六、 --- ### 一、 ### 二、 ### 三、 ### 四、 ### 五、 ### 六、 ### 七、 ### 八、 ### 九、 ### 十、 ### 十一、 ### 十二、 ### 十三、 ### 十四、 ### 十五、 ### 十六、