# Javascript 第五章 函式的運用
###### tags: `JS X Codeshiba`
## 甚麼是函式
**函式:** 是一種五臟俱全的 **`陳述式組合`**,會被視為一個單位來執行,可以把他想成是**副程式**
每個函式都有一個本文
```javascript=
function sayHello(){
//這是本文,他會從左邊的大括號開始
console.log("Hello World");
// 在右邊的大括號結束
}
```
這是一個**宣告函式**的範例: 我們宣告了一個名為sayHello的函式
但是宣告不會被執行,我們必須使用函式的名稱與括號來**呼叫函式(call)**
也稱為運行(running)、執行(executing)、引用(invoking)或指派(dispatching)
```javascript=
sayHello(); //主控台就會印出Hello World!
```
## 回傳值
在函式的本文中,return關鍵字會立刻中止函式,並且回傳指定的值
如果沒有return,那麼回傳值就會是undefined
```javascript=
function getGreeting(){
return "Hello world!";
}
```
## 呼叫 v.s. 參考 (把資訊帶出)
```javascript=
//以下是呼叫範例
const f=getGreeting;
f(); //"Hello World!"
//以下是參考範例
const 0={};
o.f=getGreeting;
o.f(); //"Hello World!"
const arr = [1,2,3];
arr[1]=getGreeting; //現在arr是[1, function getGreeting(), 2]
arr[1](); //"Hello, World!"
```
## 函式引數(把資訊帶入)
如何把資訊傳入函式呢? 把資訊傳入函式呼叫式的主要機制式函式引數(有時稱為參數)
```javascript=
//這是一個接受兩個引數,並且回傳兩個數字的平均值的函式
function avg(a,b){
return (a+b)/2;
}
```
當函式被呼叫時,引數會吸收值,然後轉換成**實際引數**
```javascript=
avg(5,10); //7.5
```
**引數只會在函式裡面生存**,就算他們名稱與函式外的變數名稱相同
```javascript=
const a=5, b=10;
avg(a,b);//這裡的a, b跟上面的a, b是沒有關係的
```
```javascript=
function f(x){
console.log(`inside f: x=${x}`);
//3
x=5;
console.log(`inside f: x=${x}(after assignment)`);
//5
}
let x = 3;
console.log(`before calling f: x=${x}`);
//3
f(x);
console.log(`after calling f: x=${x}`);
//3
```
引數x(函式內)跟變數x(函式外)有所不同
當x在f裡面被賦值,會指向一個新的、不同的物件
函式外的x仍然會指向原本的物件
### 引數構成函式嗎?
f()、f(x)是不同的函式,後者也與f(x,y)是不同的函式
有一個函式叫做f時,可以用0、1或10個引數來呼叫,而且呼叫的還是同一個函式
如果沒有提供引數,他們會私下接收undefined值
```javascript=
function f(x){
return `in f: x=${x}`;
}
console.log(f()); //undefined
console.log(f(2)); //in f: x=2
```
### 解構引數
```javascript=
function getSentence({subject, verb, object}){
return `${subject} ${verb} ${object}`;
}
const o = {
subject: "I",
verb: "love",
object: "JavaScript",
}
console.log(getSentence(o));
//I love JavaScript
```
如果變數無法對應被傳進來的物件中的特性,會得到undefined
解構陣列也是可以的
```javascript=
function getSentence([subject, verb, object]){
return `${subject} ${verb} ${object}`;
}
const arr =["I", "love", "JavaScript"];
console.log(getSentence(arr));
```
也可以使用擴張運算子(...)來蒐集所有的引數,如果要使用擴張運算子
它必須是最後一個引數,如果放在其他引數前面,JS無法分辨它與其他引數的差別
```javascript=
function addPrefix(prefix, ...words){
const prefixedWords = [];
for(let i=0; i<words.length; i++){
prefixedWords[i] = prefix + words[i];
}
return prefixedWords;
}
addPrefix("con","verse","vex");
//["converse","convex"]
```
### 預設引數
屬於ES6的功能,可以指定引數的預設值
當沒有提供引數的值時,他們會得到undefined值,你可以指定其他的預設值:
```javascript=
function f(a, b="default", c=3){
return `${a}-${b}-${c}`;
}
console.log(f(2,3,4)); //2,3,4
console.log(f(5,6)); //5,6,3
console.log(f(a,5,8)); //跑不出來
console.log(f()); //undefined-default-3
```
## 把函式當成物件的特性
如果函式是物件的特性,則稱為**方法**,這會與一般的函式區分
```javascript=
const o = {
name: 'Wallace', //原始特性
bark: function(){return 'Woof!'; }, //函式特性(方法)
}
console.log(o.name,o.bark);
```
## this關鍵字
this關鍵字跟物件特性函式有關,當呼叫方法時,this關鍵字就代表呼叫的方法所屬的物件值
```javascript=
const o = {
name: 'Wallace',
speak(){return `My name is ${this.name}!`;},
}
//當我們呼叫o.speak的時候,this關鍵字會綁定o
o.speak(); //My name is 'Wallace'!
```
this的綁定是根據:
**呼叫**函式的方法(o)
宣告函式的方法(x)
```javascript=
const o = {
name: 'Wallace',
speak(){return `My name is ${this.name}!`;},
}
const speak = o.speak;
speak === o.speak;
console.log(speak()); //My name is !
// 呼叫函式的方式變了,JS不知道函式是在o裡面宣告的,所以this會綁定undefined
console.log(o.speak()); //My name is Wallace!
// 呼叫函式的方式有明確的o,所以JS知道是在o裡面宣告的,所以this會被綁定,因此出現Wallace
```
### 注意事項
在嵌套的函式中使用this變數時,有一種經常讓人犯錯的地方,就是this會被綁到其他地方(全域物件或成為undefined)
```javascript=
const o = {
name: 'Julie',
greetBackwards: function(){
function getReverseName(){
let nameBackwards = '';
for(let i=this.name.length-1; i>=0; i--){
nameBackwards += this.name[i];
}
return nameBackwards;
}
return `${getReverseName()} si eman ym, olleH`;
},
};
o.greetBackwards();
```
此時可以把第二個變數指派給this:
```javascript=
const o = {
name: 'Julie',
greetBackwards: function(){
const self = this;
function getReverseName(){
let nameBackwards = '';
for(let i=self.name.length-1; i>=0; i--){
nameBackwards += self.name[i];
}
return nameBackwards;
}
return `${getReverseName()} si eman ym, olleH`;
},
};
console.log(o.greetBackwards());
```
## 函式運算式與匿名函式
一般來說,匿名函式會有:
1. 本文(定義函式要做的事)
2. 識別碼(讓我們可以呼叫)
要放入函式內的參數用函式來表達,這則稱為匿名函式

## 表達式(Expression)與陳述式(Statetment)
表達式(Expression) 會回傳值
1+1=2
陳述句(Statement) 不會回傳值
function()
因為變數提升(hoisting)的特性,所以陳述式可以把原先在下方的function過程直接拿來套用,但是屬於**賦值**過程的表達式不行
[參考文章_Huli_我知道你懂 hoisting,可是你了解到多深?](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/)

函式很重要,以下分為幾個階段
1. 內建函式
```javascript=
alert("Hello World")
```
2. 函式的基礎
3. 設計基礎
```javascript=
function test(){
var sum=0;
for(var n=0;n<=100;n++){
sum=sum+n
}
alert(sum);
}
```
可以加入**參數名稱**,想清楚要呈現甚麼樣的值
```Example2
<script>
function test(message){
alert(message);
}
test("Hello World");
</script>
```
4. 呼叫使用階段
```Example1
test();
```
傳入函式的**參數資料**
```Example2
test("Hello World");
```
5. 整合範例-如何做一個可以自動從1加總到任意數的函式
```
<script>
var sum=0;
function Getsumup(){
for(var n=1;n<=100;n=n+1){
sum=sum+n;
}
alert(sum);
return sum;
}
var result=Getsumup()
alert(result);
</script>
```
## 第二種寫法
函式是一種資料,也可放入變數裡面
```
<script>
var add=function(n1,n2){
//return n1+n2;
alert(n1+n2);
}
var test=add;
test(1,2);
</script>
```
## 函式的注意事項
不可以重複宣告
var x = 3
var x = 5
這很不ok,會錯亂
## 全域變數 & 區域變數
變數宣告的方法
1. var
2. const
3. let
但是可以再函式內宣告,因為函式的{}內部是一個新的空間
最外層: 全域變數
次一層: 區域變數
在Function裡面宣告為**區域變數**

變數屬性範例

## 回呼函數

## 匿名函式
## 立即函式(IIFE)
全名: Immediately Invoked Functions Expressions
可以立即呼叫的函式表達式
表達式=>會回傳值
使用函式表達式以後立即執行
結構如下

## 變數提升(Hoisting)
不管你在哪裡做的變數,都會被作為在第一行做宣告
先來看看let和var的不同

再來是各個變數在不同宣告的差異

Undefined 不等於 Not defined!!

## 匿名函式