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