The Most Familiar Stranger - JavaScript - 函式
Tags: javascript, beginners, chinese
Published: 2023-05-07 07:37:56

[分享記錄檔 YouTube](https://www.youtube.com/watch?v=VF9NREezuKI&list=PLal-nRIQU5sunSCOSeZZEzUrXUXF22gep&index=8)

# 函式

JavaScript 中的函式是一種可重複使用的程式碼，它可以接受參數並執行特定的任務。函式可以在程式中多次呼叫，這樣可以使程式更為彈性，更容易維護。

## 如何宣告函式

在 JavaScript 中，可以使用`function` 關鍵字來宣告一個函式。

以下是一個簡單的函式宣告範例：

```jsx
function add(a, b) {
  return a + b;
}
```

在此範例中，`add` 函式接受兩個參數，並將它們相加後返回結果。函式可以被呼叫多次，並且每次呼叫時可以傳入不同的參數值。

## 記憶體的變化

當函式被呼叫時，JavaScript 引擎會為該函式創建一個新的執行上下文 ( Execution Context )。該執行上下文包含函式中聲明的所有變數、參數和函式本身。當函式執行完畢並返回結果時，該執行上下文將被釋放，所有在該函式中聲明的變數和參數也將被清除。

## 執行上下文 ( Execution Context )

在 JavaScript 中，每當執行一個函式時，JavaScript 引擎都會創建一個新的執行上下文。

JavaScript 中的執行上下文是一種無名物件，它包含了以下三個屬性：

1. **Variable Object ( 變數物件 )**: 儲存該執行上下文中所宣告的變數、函式和參數等。
2. **Scope Chain ( 作用域鏈 )**: 它是用來尋找變數和函式定義的一個層層包含的列表。作用域鏈在函式被定義時就已經確定了。
3. **this** : 它指向當前執行上下文所屬的物件。

當 JavaScript 引擎執行一段程式碼時，它會創建一個全域執行上下文 ( Global Execution Context )。在瀏覽器中，全域執行上下文通常關聯到 Window 物件。接下來，每當 JavaScript 引擎執行一個函式時，它會創建一個新的執行上下文。

以下是一個簡單的範例，說明了執行上下文如何運作：

```jsx
var x = 1;

function add(y) {
  var result = x + y;
  return result;
}

console.log(add(2)); // 3

```

在此範例中，當 `add` 函式被呼叫時，JavaScript 引擎會創建一個新的執行上下文，其中包含了變數 `y` 和 `result`。此外，由於 `add` 函式是在全域範圍內被定義的，因此它的作用域鏈會包含全域作用域鏈。

當 `add` 函式執行完畢並返回結果時，JavaScript 引擎會釋放該執行上下文，並將該函式的結果返回到 Call stack 中。

### Call stack

JavaScript 中的呼叫堆疊（call stack）是一種用於追蹤函式執行的機制。當函式被呼叫時，它會被添加到呼叫堆疊的頂部。當函式執行完畢並返回結果時，它將從堆疊中彈出，讓下一個函式繼續執行。JavaScript 引擎使用呼叫堆疊來確保函式執行的順序和一致性。

以下是一個簡單的例子，說明了呼叫堆疊如何運作：

```jsx
function fn1() {
  console.log("a");
}

function fn2() {
  fn1();
}

function fn3() {
  fn2();
}

fn3();

```

在此範例中，當 `fn3` 函式被呼叫時，它會被添加到呼叫堆疊的頂部。當 `fn3` 函式調用 `fn2` 函式時， `fn2` 函式被添加到堆疊的頂部。當 `fn2` 函式調用 `fn1` 函式時， `fn1` 函式被添加到堆疊的頂部。當 `fn1` 函式執行完畢時，它從堆疊中彈出，讓 `fn2` 函式繼續執行。當 `fn2` 函式執行完畢時，它從堆疊中彈出，讓 `fn3` 函式繼續執行。最後，當 `fn3` 函式執行完畢時，它從堆疊中彈出，函式呼叫堆疊就空了。

以下是一個動畫，說明了呼叫堆疊如何運作：

![圖片來源：[https://vaibhavgupta.me/2018/01/20/understanding-event-loop/](https://vaibhavgupta.me/2018/01/20/understanding-event-loop/)](https://vaibhavguptame.files.wordpress.com/2018/01/callstack.gif?w=600&zoom=2)

圖片來源：[https://vaibhavgupta.me/2018/01/20/understanding-event-loop/](https://vaibhavgupta.me/2018/01/20/understanding-event-loop/)

## this 是什麼

在 JavaScript 中，`this` 關鍵字用於引用當前執行上下文中的物件。`this` 關鍵字的值取決於函式的調用方式，它可以是全域物件、函式本身或由該函式創建的新物件。

當在全域範圍內使用 `this` ****時，它會指向全域物件 (在瀏覽器中是 `Window` ****)。

以下是一個範例：

```jsx
console.log(this); // Window
```

以下是一個簡單的範例，其中 `this` 關鍵字用於引用當前執行上下文中的物件：

```jsx
const person = {
  firstName: 'John',
  lastName: 'Doe',
  fullName: function() {
    return `${this.firstName} ${this.lastName}`;
  }
}

console.log(person.fullName()); // "John Doe"
```

在此範例中，`this` 關鍵字用於引用 `person` 物件，因為函式是在 `person` 物件上調用的。

<aside>
🔑 一般的函式呼叫 JS 會預設將 this 綁定到全域物件，當從某個物件的參考屬性呼叫 this 會綁定到該物件

</aside>

```jsx
let obj = {
	function1: function() {
		console.log(this); // obj
		
		let function2 = function() {
			console.log(this); // Window
		}
		function2();
	}
};
obj.function1();
```

當使用 `call()`、`apply()` ****或 `bind()` 方法時，可以手動指定 `this` 的值。

以下是一個範例：

```jsx
const person1 = {
  firstName: 'John',
  lastName: 'Doe'
}

const person2 = {
  firstName: 'Jane',
  lastName: 'Don'
}

function fullName() {
  return `${this.firstName} ${this.lastName}`;
}

console.log(fullName.call(person1)); // "John Doe"
console.log(fullName.call(person2)); // "Jane Don"
```

<aside>
💡 `call()`、`apply()`、`bind()`  的不同

1.  `call()`、`apply()` 會立即執行函式，`bind()` 會回傳一個新的函式
2. `call(thisArg[, arg1[, ...]])` ****是列出所有參數，`apply(thisArg, [argsArray])` 是傳入陣列

</aside>

在使用 `new` ****關鍵字創建一個物件時，`this` ****會指向這個新物件。

以下是一個範例：

```jsx
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.fullName = function() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const john = new Person('John', 'Doe');
console.log(john.fullName()); // "John Doe"
```

## 箭頭函式 ( Arrow Function )

在 ES6 中，引入了箭頭函式的概念。箭頭函式是一種更簡潔的函式語法，它使用 `=>` 符號來定義函式。箭頭函式有一些特別的行為：

- 箭頭函式沒有自己的 `this` 關鍵字，它繼承了外部執行上下文的 `this` 值。
- 箭頭函式總是匿名的，因此不能使用 `function` 關鍵字來命名箭頭函式。
- 箭頭函式的 `return` 關鍵字是隱式的，因此不需要顯式地返回值。

以下是一個使用箭頭函式的簡單範例：

```jsx
const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map(number => number * 2);

console.log(doubledNumbers); // [2, 4, 6, 8, 10]
```

在此範例中，箭頭函式被用於將 `numbers` 數組中的每個數字乘以 2。這個函式使用了箭頭函式簡潔的語法，並返回一個新的數組。