JavaScript (三) === >2023/11/13 ## 誰呼叫誰就是 this - 跟寫在哪裡無關,只跟如何執行有關 - 誰呼叫,誰就是 this - 沒人呼叫,this 是全域,會出現 window - this -> {} 走 new 流程 會先做一個空物件,this 指向空物件 - 是否有箭頭函數(如果使用箭頭函數,箭頭函數沒有 List,會往外面找) - 如果使用 new,又使用箭頭函式的話...會出現`heroCreator is not a constructor` - 是否使用 apply, call, bind - 是否有使用嚴格模式(strict) ### hero 呼叫 我問你我是誰,你告訴我 hero 物件 ```javaScript const hero = { name: "cc", age: 18, action: function () { console.log(this) }, } //印出hero ``` ### 沒人呼叫 = window(全域)呼叫 ```javaScript function hi(){ function hey(){ console.log(this) } } //印出window ``` ```javaScript function hi{ function hey(){ console.loe(this) } hey() } hi() //印出window ``` ```javascript function heroCreator(name, power) { console.log(this); } new heroCreator("cc", 100); // //印出 heroCreator{} heroCreator("cc", 100); // 印出window ``` ### 如果使用箭頭函數 ## ES6 新語法 ```javascript function hi(...a) { console.log(a); } hi(1, 2, 3); ``` ### 之前的做法? - 在箭頭函數內沒有 arguments,使用 arguments 會找不到 ```javascript function hi() { console.log(arguments); } hi(1, 2, 3); ``` ## 範例 要印出 btn,僅能用一般函式,用箭頭函式就無法執行 ```javascript const btn = document.querySelector("#go"); btn.addEventListener("click", function show() { console.log(this); }); ``` ### 使用閉包 箭頭函式會往外找,以下案例會出現 btn ```javascript const btn = document.querySelector("#go"); btn.addEventListener("click", function () { setTimeout(() => { console.log(this); }, 1000); }); ``` ### apply 方法 - 所有 function 都有 apply 方法 - apply 會綁架,如範例,apply 會把 this 指向 hero ```javascript const hero = { name: "cc" }; function hi() { console.log(this); } hi.apply(hero); //印出{ name: 'cc' } ``` ### call 方法 - 如範例,把 hero 帶進去,當 this 用 ```javascript const hero = { name: "cc" }; function hi() { console.log(this); } hi.call(hero); ``` call 範例 ```javascript const hero = { hp: 100, mp: 30, attack: function () { console.log("attack!!!"); }, }; const mage = { hp: 50, mp: 100, attack: function () { console.log("attack~~"); }, heal: function () { this.hp += 30; }, }; console.log(mage.hp); mage.heal(); console.log(mage.hp); console.log(hero.hp); mage.heal.call(hero); console.log(hero.hp); ``` ## call 和 apply 的差別? - call ```javascript function hi(a, b, c) { console.log(a, b, c); console.log(this); } hi.call([], 1, 2); //1 2 undefined //[] ``` - apply 要用陣列 > mdn:這個函式的語法和 call() 幾乎一樣,最大的不同是 call() 接受一連串的參數,而 apply() 接受一組陣列形式的參數。 ```javascript function hi(a, b, c) { console.log(a, b, c); console.log(this); } hi.apply([1, 2, 3], [1, 2]); //1 2 undefined //[ 1, 2, 3 ] ``` ### bind 會要綁定 this,**不會馬上執行**,會回傳一個新 function 回來,會改變 this 指向 範例: ```javascript function hi() { console.log(this); } const v = [1, 2, 3]; const newHi = hi.bind(v); v[0] = "x"; console.log(v); newHi(); //[ 'x', 2, 3 ] //[ 'x', 2, 3 ] ``` ### 閉包是什麼? > mdn:閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。 - 把周圍環境變數捕進去 function,如範例中 let,原本使用 let 跑完 i 就不見了,js 會把 0,1,2 包進去 - js 會判斷,閉包行為會不斷發生 範例: 使用 var 迴圈跑完 i 還是會在 ```javascript for (var i = 0; i < 3; i++) { setTimeout(function () { for (var i = 0; i < 3; i++) { console.log(i); } }, 1000); } //答案 3 3 3 ``` 使用 let 迴圈跑完 i 不見,js 會把 0,1,2 包進去 ```javascript for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 1000); } //答案 0 1 2 ``` 也可以這樣執行 ```javascript for (let i = 0; i < 3; i++) { let j = i; setTimeout(function () { console.log(j); }, 1000); } ``` ## IIFE - Immediately invoked Function 立即執行的函數表示法 ```javascript (function (x) { let a = 1; console.log(123); console.log(a); })(123); //123 //1 ``` ### “use strict” 嚴格模式 開啟方式`user strict`,也可以開在 function 內 ```javascript function hi() { "use strict"; console.log(this); } //沒有回傳東西 ``` ### 為什麼要寫成`"use strict"` 為了因應老舊的瀏覽器無法升級的關係, 若無法使用此功能,則會認為這是普通的字串而已 ## Optional chaining (?.) 當需要存取一個函數,而這函數並不存在時,則會回傳 undefined ## get, set set 物件可讓你儲存任何類型的唯一值(unique),不論是基本型別(primitive)值或物件參考(references)。 get 語法會將物件屬性,綁定到屬性被檢索時,所呼叫的函式。 呼叫函數時省略小刮號 ```javascript const hero = { //getter setter get age() { return 18; }, set newName(str) { this.name = str; }, }; console.log(hero.age); //hero.newName("cc"); hero.newName = "cc"; ``` ## 測試 ## 自動化測試 寫程式去驗證你寫的程式是對的 先寫測試(規格)再把程式碼補回來 ## Test Driven Development(TDD) - 重點為開發 ### 什麼是測試? 就是寫一段 code,去驗證另外一段 code 能不能跑 ### 測試的目的 確保程式往預期的方式走 ### 測試 測試不存在的功能,假設他可正常運作 可以使用[jestjs](https://jestjs.io/) 也可以在終端機運行以下代碼執行安裝 ``` 1. npm i -y 2. npm i -d jest 3. package.json改寫"test": "jest" 4. 新建__test__資料夾,在此資料夾內新增bank_spec.js檔案 5. terminal執行npm run test ``` 實際寫看看! ```javascript class BankAccount { constructor(amount) { this.amount = amount; } deposit(amount) { this.amount += amount; } get balance() { return this.amount; } } test("可以存錢", () => { // 3A, Arrange, Act, Assert // 1. 生帳號,開戶10 const account = new BankAccount(10); // 2.存10元 account.deposit(20); // 3.餘20元 expect(account.balance).toBe(30); }); ``` ### constructor ```javascript class Cat { constructor(a, b) { this.a = a; this.b = b; } } const kitty = new Cat(1, 2); console.log(kitty); //Cat { a: 1, b: 2 } ``` ### 把測試寫更多一點! ```javascript class BankAccount { constructor(amount) { this.amount = amount; } deposit(amount) { if (amount > 0) { this.amount += amount; } } enough(amount) { return amount <= this.amount; } withdraw(amount) { if (amount > 0 && this.enough(amount)) { this.amount -= amount; return amount; } return 0; } get balance() { return this.amount; } } test("可以存 10 元", () => { const account = new BankAccount(10); account.deposit(10); expect(account.balance).toBe(20); }); test("可以存 20 元", () => { const account = new BankAccount(10); account.deposit(20); expect(account.balance).toBe(30); }); test("不可以存 0 元或是小於 0 元的金額", () => { const account = new BankAccount(10); account.deposit(-20); expect(account.balance).toBe(10); }); test("可以領錢", () => { const account = new BankAccount(10); const amount = account.withdraw(3); expect(amount).toBe(3); expect(account.balance).toBe(7); }); test("不能領 0 元或是小於 0 元的金額", () => { const account = new BankAccount(10); const amount = account.withdraw(-5); expect(amount).toBe(0); expect(account.balance).toBe(10); }); test("不能領超過本身餘額", () => { const account = new BankAccount(10); const amount = account.withdraw(20); expect(amount).toBe(0); expect(account.balance).toBe(10); }); ```