---
# System prepended metadata

title: JavaScript （三）
tags: [javaScript]

---

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