# 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. 識別碼(讓我們可以呼叫) 要放入函式內的參數用函式來表達,這則稱為匿名函式 ![](https://i.imgur.com/ug6XH84.png) ## 表達式(Expression)與陳述式(Statetment) 表達式(Expression) 會回傳值 1+1=2 陳述句(Statement) 不會回傳值 function() 因為變數提升(hoisting)的特性,所以陳述式可以把原先在下方的function過程直接拿來套用,但是屬於**賦值**過程的表達式不行 [參考文章_Huli_我知道你懂 hoisting,可是你了解到多深?](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/) ![](https://i.imgur.com/yHbgQph.png) 函式很重要,以下分為幾個階段 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裡面宣告為**區域變數** ![](https://i.imgur.com/reOmX6h.png) 變數屬性範例 ![](https://i.imgur.com/3lbsSYS.png) ## 回呼函數 ![](https://i.imgur.com/eWGmF1o.png) ## 匿名函式 ## 立即函式(IIFE) 全名: Immediately Invoked Functions Expressions 可以立即呼叫的函式表達式 表達式=>會回傳值 使用函式表達式以後立即執行 結構如下 ![](https://i.imgur.com/jSVXp6C.png) ## 變數提升(Hoisting) 不管你在哪裡做的變數,都會被作為在第一行做宣告 先來看看let和var的不同 ![](https://i.imgur.com/o54z4Xp.png) 再來是各個變數在不同宣告的差異 ![](https://i.imgur.com/ehkFx7p.png) Undefined 不等於 Not defined!! ![](https://i.imgur.com/mgVPZwh.png) ## 匿名函式