---
# System prepended metadata

title: 函式
tags: [' 六角筆記王', JavaScript]

---

---
tags:  JavaScript, 六角筆記王
title: 函式
---

# 函式
若要有效的處理資料，就一定會用到函式，這篇記錄函式有怎樣的特性與運用概念。目前有整理到的如下:
- 函式型別
- 立即函式
- 函式的參數使用，不同的函式會有不同的內建關鍵字，常見的有
    - `arguments` (ES6箭頭函式則無此參數)
    - `this` (ES6箭頭函式則無此參數)
    - 自訂參數
- 閉包
    - 範圍鏈
    - 函式與提升 (Hoisting)
    - 函式與變數利用
    - 閉包的私有函式

## 宣告函式
- 使用 `function` 來宣告一個函式。
- 具名函式，宣告函式後，給定一個名稱以利後續呼叫。
- 匿名函式，宣告函式後，不須給定名稱，常用於立即函式與變數賦予值上。
    - 名稱要給也是可以，但通常不會這麼做。
- ES6中可以使用箭頭函式。
- 雖然型別為 `function`，但它其實是物件的一種。
```javascript 
/* 具名函式 */
function fnA(){
  return '具名函式'
}
fnA()

/* 匿名函式 */
var fnB = function (){
  return '匿名函式'
}
fnB()

/* ES6箭頭函式 */
let fnC = () => {
  return '箭頭函式'
}

console.log(typeof fnA) // "function"
```
## 函式與型別
通常我們會利用 `typeof()` 來檢查目前值為哪一種型別，若對函式做檢查，會得到 `"function"`，但很多技術文章都說，函式其實也是一種物件，那要怎麼知道它的確是物件的一種呢?

- 利用物件原型方式看子型別為何，會得到 `"[object Function]"`
- 將函式當作物件新增屬性，console.log 可以正確取值
- 將函式使用 `new` 這個運算子做為物件，可以在 console.log 的結果中往內層展開並找到新增的屬性
```javascript 
/* typeof 型別檢查為 funciton */
const fn = () => { return 1 }
console.log(typeof fn) // 'function'

/* 利用物件原型來看函式屬於哪一種子型別 */
console.log(Object.prototype.toString.call(fn))
// "[object Function]"

console.log(fn.prototype) // Object { 許多屬性 } 

/* 函式可以新增屬性 */
fn.b = '新增屬性值'
console.log(fn.b) // '新增屬性值'

/* 將函式以 new這個運算子，成為一個建構式函式 */
// 可以在原型物件(__proto__)底下的 constructor中，發現新增的屬性 b
console.log(new fn)

// fn{
//   __proto__: {
//     constructor: fn {
//       b = '新增屬性值'
//     }
//   }
// }
```


## 立即函式
- 一般函式需要呼叫它才會執行
    - `函式名稱()`
- 立即函式會立刻執行，給定名稱沒有意義
    - `( 名稱(){} )()`
    - `( (){} )()`
```javascript 
/* 一般函式 */
function fn(a){
  console.log(a)
}

/* 立即函式 */
// 它會立刻執行，所以也可以寫成匿名函式
(function (a){
  console.log(a)
})('函式')

/* 箭頭與立即 */
( a => {console.log(a)} )('函式')
```

## 函式與參數
一段函式中，常見的內建參數有以下這些 : 
- arguments
- this
- 自訂參數

```javascript 
var a = '全域物件中的變數'
function fn(a){
  console.log(a, arguments , this.a)
}
fn('自訂參數')
// '自訂參數'
// ['自訂參數']，它為類陣列
// '全域物件中的變數'，為什麼不是 '自訂參數'，在後面 this會有說明
```

### 參數關鍵字 arguments
- 能接收所有傳入的值
- 它為類陣列，也就說它沒有陣列的原型方法可以使用
- 其型別為 `"object"`

```javascript
function sum(a, b) {
  console.log(arguments)
  console.log(typeof(arguments))
}
sum(2, 4, 6)
// Arguments{ 
//   [2, 4, 6],
//   callee: f sum(),
//   length: 3
// }
// "object"
```

### 參數關鍵字 this
參考自己整理 this筆記，this在不同位置中會指向誰。
- [箭頭函式與 this(上)](https://hsuan777.github.io/2020/11/02/javascript/ES6%E7%AE%AD%E9%A0%AD%E5%87%BD%E5%BC%8F%E8%88%87this%E4%B8%8A/)
- [箭頭函式與 this(下) - call、apply、bind](https://hsuan777.github.io/2020/11/02/javascript/ES6%E7%AE%AD%E9%A0%AD%E5%87%BD%E5%BC%8F%E8%88%87this%E4%B8%8B/)

#### this 與簡易呼叫
還記得 `this` 只看在哪裡被呼叫嗎? 又怎樣算是簡易呼叫(simple call)? 
來看以下例子 : 
```javascript 
var a = '全域物件'

function fnA(){
  console.log(this.a)
}

function fnB(){
  var a = '函式內變數'
  function fnC(){
    console.log(this.a)
  }
  fnC()
}
fnA() // '全域物件'
fnB() // '全域物件'
```

為什麼兩個都是指向全域?
- `fnA()` 在全域環境中被呼叫，且函式在全域環境下被定義，這點很容易看出來，就算把 `this` 拿掉，也會尋找外層的變數 a。
- `fnB()` 被呼叫後，執行了 `fnC()` ，它明明在 `fnB()` 內被執行，為什麼 `this` 還是指向全域? 沒關係，繼續往下看另一個例子。

```javascript 
var a = '全域物件'
var obj = {
  a: '物件屬性值',
  fnA: function(){
    console.log(this.a)
  },
  fnB: function(){
    var a = '函式內變數'
    function fnC(){
      console.log(this.a)
    }
    fnC()
  }
}

obj.fnA() // "物件屬性值"
obj.fnB() // "全域物件"
```

為什麼執行物件中的兩個函式，結果會不一樣，照理說要指向物件中的屬性 `obj.a` 吧?
- 物件中的 `fnA()`，`this` 正確指向了該物件
- 物件中的 `fnB()` 執行後，再執行 `fnC()`，`this` 卻指向了全域物件?
    - 執行 `obj.fnA()` ，是由 `obj` 呼叫 `fnA()`
    - 執行 `obj.fnB()` ，是由 `obj` 呼叫 `fnB()`，接著執行 `fnC()` ，`fnC()` 被誰呼叫? 沒有，它是直接被執行的，所以它的 `this` 指向了全域。
    - 若將 `fnC()` 內的 `this.a` 改為 `a`，則會因為範圍鏈的關係往外層尋找，就會是 `'函式內變數'`。

再來複習一下 `this` 與簡易呼叫。
- 簡易呼叫是甚麼?
    - 直接呼叫，未透過任何方式取用執行。
- 簡易呼叫為全域物件下的一種? 
    - 否，是 `this` 會指向全域，而不是該函式被建立在在全域下
- 那些例子屬於簡易呼叫?
    - callback function 
    - 立即函式
    - 原型方法，例如陣列的 `forEach`，此為繼承共用的原型方法，屬於直接執行

那如果要將 `fnC()` ，正確的指向物件屬性 a呢 ? 來改寫上面的例子，
```javascript 
var a = '全域物件'
var obj = {
  a: '物件屬性值',
  fnA: function(){
    console.log(this.a)
  },
  fnB: function(){
    var a = '函式內變數'
    // 這裡的 this 會指向該物件
    var vm = this
    function fnC(){
      // 閉包的原理，讓還需用到的變數，將它關在函式中，不被釋放
      console.log(vm.a)
    }
    fnC()
  }
}

obj.fnA() // "物件屬性值"
obj.fnB() // "物件屬性值"
```
 
為什麼 `fnB()` 將 `this` 賦予給一個變數時，就不會指向全域，而是會指向物件呢?
- `fnB()` ，是由物件 `obj` 所呼叫的，就跟 `fnA()` 一樣，他們的 `this` 都會指向該物件。
- 在 `fnB()` 內宣告一個變數，將指向物件的 `this` 賦予給該變數，`fnC()` 再利用此變數時，就會正確的指向物件 `obj`。
- 後面的段落會說明閉包的概念。


#### this 與DOM 
- `console.dir` 可以看到它原本的物件有哪些屬性
- 可以使用 `this` 指向 DOM的單一元素
```javascript 
console.dir('某個標籤元素')
```
#### this 與 call、apply、bind
- call與 apply會立刻執行，bind則需要呼叫。
- **非嚴格模式傳入 `null`、`undefined`，則會指向全域物件**。
- `this` 為物件型別，也就是說傳入後會變成包裹物件(建構式)。
    - 若傳入的是數字，會變成 `Number()`
    - 若傳入的是字串，會變成 `String()`

```javascript 
var c = 5
function fn(a, b){
  console.log(this)
  console.log(this+a)
  console.log(this+b)
  console.log(this.c)
}

fn.call(1, 2, '3')
// Number 1 
// 3
// "13"
// undefined

fn.call(null, 2, '3')
// Window 
// "[object Window]2"
// "[object Window]3"
// 5
```

#### 嚴格模式
- 加入 `use strict`
- 不會影響不支援嚴格模式的瀏覽器
- 可依據執行環境設定 `use strict`
- 透過拋出錯誤的方式消除ㄧ些安靜的錯誤(消除小錯誤)
- 禁止使用ㄧ些有可能被未來版本 ECMAScript定義的語法
- 在這些方法中的 `this` ，預設值是 `undefined`，若未傳入值又去呼叫它，就會是 `undefined`。

```javascript 
function fn(a, b){
  'use strict'
  // 在嚴格模式下的 call、apply、bind 不會變成建構式物件
  console.log(this)
  console.log(this+a)
  console.log(this+b)
  console.log(this.c)
}

fn.call(null, 2, '3')
// null
// 2
// "null3"
// Cannot read property 'c' of null 
```

### 函式與自訂參數
#### 參數傳入值
- 當函式執行時，只會根據位置傳入值
- 在函式中，參數所運用的地方才會是對應的
```javascript 
function fn(a, b, d, e){
 console.log(d, e, a, b)
}
fn('a', 'b', 'c', 'd')
// "c" "d" "a" "b"
```


#### 參數傳入物件
當利用函式內的程式碼修改傳入的物件時，會依據傳參考特性修改原物件的屬性值，若物件在函式內做了深層複製的話則不會。

#### 參數傳入函式( callback function )
除了可以傳入值、物件、陣列，也可以將另一個函式傳入。

```javascript 
// 函式參數傳入函式
function callSomeone(name, a){
  console.log(name, '你好'，a)
}
function fnB(fn){
  fn('小明', 1)
}
fnB(callSomeone)
```



## 參考來源
>1. [六角學院 - JS核心篇]
>2. [cythilya - 你懂 JavaScript 嗎？#4 型別（Types）](https://ithelp.ithome.com.tw/articles/10200841)
>3. [Kuro Hsu - 重新認識 JavaScript: Day 10 函式 Functions 的基本概念](https://ithelp.ithome.com.tw/articles/10191549)
>4. [Ray - 有點長的淺談 JavaScript function 函式](https://w3c.hexschool.com/blog/cb6e361)
>5. [Huli - 淺談 JavaScript 頭號難題 this：絕對不完整，但保證好懂](https://blog.techbridge.cc/2019/02/23/javascript-this/)
