# 十二、 宣告各種函式 ###### tags: `JavaScript` `JS101` `2020七月第二週` `進度筆記` `Lidemy心得` `7/8` --- ## function 起手式 一般宣告函式的方式: function hello() { console.log('hello') } return { } 函式是 primitive data value , 因此我們可以: var hello = function() { console.log('hello') } 會跟上個式子很相似。 --- 而如果: function print(anything) { console.log(anything) } print(123) // 會印出 123 。 傳 `print(123)` 參數到 console.log 就會印出 123。 接著: function print(anything) { //這邊的 anything 有可能是個 function ; console.log(anything) } function hello() { // 假設新增了一個 function 叫作 hello ; } print(hello) // 會印出 Function: hello 。 可以把最後一行 hello 往上傳進去,再將 function 當作參數傳進去。 然後: function print(anything) { // function print(anything) { 是物件,這邊的 anything 有可能是個 function ; console.log(anything) // 這裡可以傳一個 function 進來,相等於下面 "會變成:" 式子的 anything() ; } function hello() { // function 前面有個 log: ; 等同這行 function 不會被執行,只是被 log ; console.log('hello') // 'hello' 是第一行的物件 function print(anything) , 當這個函式是物件裡面的 method 時,這時候的 this 就會指稱到包含這個 method 的物件; print(hello) // 會印出 Function: hello 。 會變成: function print(anything) { // 讓 anything() 變成 function hello() ; anything() // 成為 function 執行他。 } function hello() { console.log('hello') } print(hello) // 把 hello 往上傳進去 // 會印出 hello 。 必須在 函式(參數) {區塊內才能放另外一個函式} ,其實為: 最後一行的 `print(hello)` 將 hello 參數設為 function 傳進去; 而第一行 `function print(anything) {` 的 anything 也就是一個參數,也是一個 function , 相等於第五行的 `function hello()` ,因此如果將最後一行 `print(hello)` 改成 `print(123)` 就會出錯。 示範: function transform(x, transformFunction) { return transformFunction(x) // 這一行意思是執行 function test(x) } function test(x) { // x 接受一個參數 return x*2 // 回傳 x*2 } console.log( transform(10, test) // 這邊的 (10, test) 即第五行的 function test(x) { ; ) // 會印出 20 。 由於最後一行 `transform(10, test)` 傳到第一行的 x 相等於 10 , 然後最後一行的 test 被傳到第五行的 function ; 而第一行 transformFunction 相當於參數,也相當第五行的 function , 然後執行 x*2 。 再換一個方式: `transform([1, 2, 3], double)` 將任何東西都 *2 => [2, 4, 6]。 function transform(arr, transformFunction) { // 因最結尾的參數也是函式 transformFunction 被傳過來,這會對 arr 的每個元素都 *2 ; var result = [] for(var i=0; i<arr.length; i++) { // 因結尾的參數被放到第一行,所以迴圈內的值 result.push(transformFunction(arr[i])) // transformFunction 會建立一個 function 把 (arr[i]) 丟進去,再把他 push 到結果內; } return result // 回傳 result 回去; } function double(x) { // x 接受一個參數 return x*2 // 回傳 x*2 ; 如果改成 *3 就會輸出陣列會變成 [3, 6, 9] ; } console.log( transform([1, 2, 3], double) // 這邊的 ([1, 2, 3], double) 會回傳一個參數也是函式 即 function double(x) ; ) `transform([1, 2, 3], double)` 就會被放到第一行的 `function transform(arr, transformFunction) {` 這是 JS 的重要基礎步驟,結尾的 `console.log()` 不只能回傳數字、字串,還能回傳參數和函式。 ## 宣告一個變數等於一個函式: function transform(arr, transformFunction) { // 因最結尾的參數也是函式 transformFunction 被傳過來,這會對 arr 的每個元素都 *3 ; var result = [] for(var i=0; i<arr.length; i++) { // 因結尾的參數被放到第一行,所以迴圈內的值 result.push(transformFunction(arr[i])) // transformFunction 會建立一個 function 把 (arr[i]) 丟進去,再把他 push 到結果內; } return result // 回傳 result 回去; } var triple = function() { // 後面這個 function 是沒有名字的,因為它被賦予了變數 triple ,由變數決定; return x*3 } function triple(x) { // x 接受一個參數 return x*3 // 回傳 x*2 ; 如果改成 *3 就會輸出陣列會變成 [3, 6, 9] ; } console.log( transform([1, 2, 3], triple) // 這邊的 ([1, 2, 3], double) 會回傳一個參數也是函式 即 function double(x) ; ) /// 這印不出來,只是示範將變數後的參數位置移開; /// 可以將 var triple = function() 移到 transform([1, 2, 3], triple) 取代 triple 的位置: function transform(arr, transformFunction) { // 因最結尾的參數也是函式 transformFunction 被傳過來,這會對 arr 的每個元素都 *3 ; var result = [] for(var i=0; i<arr.length; i++) { // 因結尾的參數被放到第一行,所以迴圈內的值 result.push(transformFunction(arr[i])) // transformFunction 會建立一個 function 把 (arr[i]) 丟進去,再把他 push 到結果內; } return result // 回傳 result 回去; } function triple(x) { // x 接受一個參數 return x*3 // 回傳 x*2 ; 如果改成 *3 就會輸出陣列會變成 [3, 6, 9] ; } console.log( transform([1, 2, 3], function(x) { // 後面這個 function 是沒有名字的,因為它被賦予了變數 triple ,由變數決定; return x*3 }) // 這邊的 ([1, 2, 3], double) 會回傳一個參數也是函式 即 function double(x) ; ) // 會印出 [3, 6, 9] 。 將變數等於一個函式,並將它函式的名稱隱藏起來的作法即 "匿名函式 (anonymous function) , 好處是可以直接改最後 return 的值。 [1. 匿名函式](https://pjchender.blogspot.com/2016/03/javascriptfunction-statements-and.html) [2. 匿名函式和 this](https://pjchender.blogspot.com/2016/03/javascriptthisbug.html) --- # 十三、參數和引數 ## 常常名詞混用的兩種 作 function 的時候常常會不小心誤會 "參數(parameter)" 和 "引數(argument)" , 如 `function transform(arr, transformFunction)` 參數其實是括號內的 arr 和 transformFunction , 即他在 function 長甚麼樣子。 而引數是真正傳進去的東西,如: function add(a, b) { // function add(a, b) 括號內的為參數; return a+b } add(3, 5) // 真正傳進去的值為引數; 宣告後的引數: function add(a, b) { // function add(a, b) 括號內的為參數; return a+b } var c = 10 var d = 20 add(c, d) // 真正傳進去的值為引數; 在 call function 的為引數,會包含所有你放入function中的參數值。 但參數和引數其實都是傳進去的值。 --- ## 背後的影武者: arguments JS 在每個 function 後加入了 arguments: function add(a, b) { console.log(arguments) // 會把 2, 5 引數印出來; return a+b } console.log(add(2, 5)) 會得到 [Arguments] { '0': 2, '1': 5 } 7 Argument 的第 0 個為第一行的 a , 第一個為 b 即最後一行的回傳值 (2, 5) ; 因此第一行的括號內也可以甚麼都不寫: function add() { console.log() return arguments[0] + arguments[1] // 會把 2, 5 引數回傳,即上式的 return a+b ; } console.log(add(2, 5)) 答案也是 7 。 [1、參考引數和參數的解說:](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/A_re-introduction_to_JavaScript) [2、參考 arguments 物件解說:](https://ithelp.ithome.com.tw/articles/10192368) [3、[筆記] 談談JavaScript中函式的參數(parameter),arguments和展開運算子(spread)](https://pjchender.blogspot.com/2016/04/javascriptparameterargumentsspread.html) --- # 十四、 引數是啥? --- ## Arguments 印出所有參數 引數是個 object 物件,並不是陣列: function add (a,b ) { console.log(arguments) return a + b } console.log(add(2, 5)) 會印出 `[Arguments] { '0': 2, '1': 5 }` 7 , 且答案可以相加是 7 。 --- 數字 0 跟字串 '0' 是一樣的沒有區別: function add (a,b ) { console.log(arguments[]) // [ ] 內可以取得 [Arguments] { '0': 2, '1': 5 } 的 0 或是 1 這個 key 變數的位置; return a + b } console.log(add(2, 5)) var a = { // → 假設有個物件長這樣; b: '123' } a.b // 或是可以用 a['b'] ; 這印不出來。 --- function add (a,b ) { console.log(arguments[]) // [ ] 內可以取得 [Arguments] { '0': 2, '1': 5 } 的 0 或是 1 這個 key 變數的位置; return a + b } console.log(add(2, 5)) var a = { // → 假設有個物件長這樣; 0: '123' } a[0] // 可以這樣存取; 但其實 a 是一個物件不是陣列: function add (a,b ) { console.log(arguments) // [ ] 內可以取得 [Arguments] { '0': 2, '1': 5 } 的 0 或是 1 這個 key 變數的位置; return a + b } console.log(add(2, 5)) var a = { // → 假設有個物件長這樣; 0: '123' } a[0] // 可以這樣存取; console.log(a[0]) 會印出 123 。 ## Arguments 還有個屬性是 .length 一共可以傳幾個參數進來: function add (a,b ) { console.log(arguments.length) // [ ] 內可以取得 [Arguments] { '0': 2, '1': 5 } 的 0 或是 1 這個 key 變數的位置; return a + b } console.log(add(2, 5)) var a = { // → 假設有個物件長這樣; 0: '123' } a[0] // 可以這樣存取; console.log(a[0]) 會印出: 2 → 表示有兩個參數傳進來; 7 → 因為 return 2 + 5 ; 123 → 宣告 a 被賦予一個物件,而物件內 0= '123' 字串。 --- 清楚一點表示: function add(a, b) { console.log(arguments.length) return a + b } add(2, 5) 會印出 2 , 表示有兩個參數回傳,是一個長得很像 array 的 obj , array-like 。 --- # 十五、 function 傳參數的運作機制 ## 用個函數交換 2 變數: function swap(a, b) { // 傳 a 和 b 進去函式; /* var a = number1 → 因為 a 和 b 是複製來的,所以記憶體位置跟 number1 和2 不同; var b = number2 → 傳進來後 a 和 b 的確變了,但不會影響外部 var number1 = 10 和 var number2 = 20 的值。 其 value 一樣,但是是不同的 key 變數。 */ var temp = a // a 賦予暫存變數; a = b b = temp // 交換 2 變數需有第三方暫存,; console.log('a, b:', a, b) // 這邊的確有交換 , a, b: 20 10 ; } var number1 = 10 var number2 = 20 console.log(number1, number2) // 這裡為 10 20 , 這邊為最開始傳進去第一行函式時期時有複製一份; swap(number1, number2) console.log(number1, number2) // 印出來為 10 20 ; 印出來為: ![](https://i.imgur.com/uwjpDIf.png) 如果第五行 `console.log('a', 'b')` 的 (a, b) 這樣子直接印會出現: ![](https://i.imgur.com/12TlNXE.png) 而如果是字串 ('a', 'b') 則是: ![](https://i.imgur.com/Px3Z2Rj.png) 都是一樣的表達方式,只是印出來的值不同。 --- ## 這樣回傳是 11 還是 10 ? function addValue(obj) { // 傳個 obj 進來; /* 其實這裡有點像是物件的記憶體存取方式,第一行 var obj = a (外部的 number: 10) 會指向同一個值。 */ obj.number++ // 回傳變數(屬性) +1 ; 也因指向同一個記憶體位置,裡面的值會影響外面宣告的變數值; return 1 } var a = { number: 10 } addValue(a) console.log(a) 簡單型別如數字、字串、布林是直接 copy value 因此改不到,但物件和變數型態比較不同。 如變數並賦予給值時,變數會指向值在記憶體中的位置,若以這個值為參照,指定另一變數指向這個值時,電腦會在記憶體中新增(複製)一個新值,讓後來的這個變數指向新的值。 因此會印出一個物件: { number: 11 } 。 也因此使用函式時,數值內部的東西更動時,可能會與外部的宣告賦予變數連動,也因此有幾種形式: pass by value pass by sharing (屬於在 pass by value 底下) pass by reference (JS 內沒有) [參考文件: 深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?](https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/) --- function addValue(obj) { obj = { number: 100 } return 1 } var a = { number: 10 // 而這邊新的物件產生,指到了另一塊記憶體; } addValue(a) // 這行沒有作用,但表示下行 a 會被加入到 function addValue(obj) { 內; console.log(a) 因 `obj = {number: 100}` 是個物件,存在一個獨立記憶體中; 而僅賦予 obj 是物件,但因為函式中沒有運算,也因此回傳 1 不起作用; 最後宣告 `var a = {number: 10}` 是個物件,也是個變數,但沒有指向與第三行的 obj 同個記憶體,因此可以回傳到第一行起作用,會印出: { number: 10 } 。