最基本的函式結構
y = f(x) 是數學上的「函數」,它跟程式裡面的「函式」是一樣的東西,都叫做「function」
f(x)
就是「函數」x
就是「參數」y
就是「回傳值」
用 return
也可以回傳一個 object
function twice(a){
return {
answer: a * 2
}
}
console.log(twice(5))
// output: { answer: 10 }
錯誤寫法:
有些人為了排版,會把要回傳的東西按 Enter 到下一行去。
但這樣寫是錯的!
因為:
如果 return
後面沒有接東西的話,JavaScript 會把 return
和 {answer: a * 2}
當成「兩句不相干的東西」,所以 return
就只會回傳 undefined
function twice(a){
return
{
answer: a * 2
}
}
console.log(twice(5))
// output: undefined
參數的取名也很重要
好的做法:取個有意義的參數名稱,像是 min
、max
不好的做法:無意義的參數名稱,像是 a
、b
function generateArray(min, max){
var arr = []
for(let i=min; i<=max; i++){
arr.push(i)
}
return arr
}
console.log(generateArray(3, 10))
function 也可以傳變數進去
function generateArray(min, max){
var arr = []
for(let i=min; i<=max; i++){
arr.push(i)
}
return arr
}
var a = 3
var b = 10
console.log(generateArray(a, b))
宣告函式的不同種方式
宣告方式一
宣告函式最常見的方式是這樣:
function greeting(){
console.log('hello')
}
宣告方式二:讓變數等於一個 function
- 先宣告一個變數
greeting
- 讓變數
greeting
等於一個 function
注意!變數後面的 function 是沒有名字的,function 的名字就是由「前面那個變數名稱來決定」
- 同樣是使用
greeting()
來執行這個 function
var greeting = function(){
console.log('hello')
}
greeting() // 執行這個 function
// output: hello
把 function 當作參數,傳進另一個 function
範例一:
把 hello
函式當作參數,傳進 print
函式,結果就會印出 [Function: hello]
(也就是:hello
是一個函式),
但是,並不會去執行 hello
這個函式,因為我只是 console.log(anything)
function print(anything){
console.log(anything)
}
function hello(){
console.log('How are you')
}
print(hello)
// output: [Function: hello]
我現在把 print
函式改成:
執行 anything()
,這樣就會去執行「傳進來的 hello
函式」了
function print(anything){
anything()
}
function hello(){
console.log('How are you')
}
print(hello)
// output: How are you
anything
參數,其實就是hello
函式,所以我就可以執行anything()
,得到hello
函式的結果
範例二:
把 test
函式當作參數,傳進 transform
函式
function transform(a, transformFunction){
return transformFunction(a)
}
function test(a){
return a * 2
}
console.log(
transform(30, test)
)
// output: 60
範例三:
對每一個陣列元素 arr[i]
都使用「我傳進去的 transformFunction
函式」
function transform(arr, transformFunction){
let newArr = []
for(let i=0; i<arr.length; i++){
newArr.push(transformFunction(arr[i]))
}
return newArr
}
function double(x){
return x * 2
}
console.log(
transform([3, 4, 5], double)
)
// output: [ 6, 8, 10 ]
也可以這樣寫:
直接傳入匿名函式(anonymous function)
「匿名函式」就是:沒有名字的 function,像是這裡的
function(x){
return x * 2
}
不需要幫函式取名字,可以把整個匿名函式直接傳進去 transform
函式
function transform(arr, transformFunction){
let newArr = []
for(let i=0; i<arr.length; i++){
newArr.push(transformFunction(arr[i]))
}
return newArr
}
console.log(
transform([3, 4, 5], function(x){
return x * 2
})
)
// output: [ 6, 8, 10 ]
參數(Parameter)與引數(Argument)
在使用 function 時,有兩個名詞常常會被混用:
- 參數(Parameter)
- 引數(Argument)
「參數」就是「在宣告 function 時,小括號裡面的東西」
「引數」就是「在呼叫 function 時,我真正傳進去的東西」
範例一:
- 在
add
這個 function,接收了兩個參數:a 和 b add
函式的引數:3 和 5
function add(a, b) {
return a + b
}
add(3, 5)
範例二:
add
函式的參數:a 和 badd
函式的引數:c 和 d- 也可以說:
add
函式的引數是 10 和 20function add(a, b){ return a + b } var c = 10 var d = 20 add(c, d)
- 也可以說:
取得引數
在 add
函式裡面,用 console.log(arguments)
就可以把「引數」給印出來
function add(a, b){
console.log(arguments)
return a + b
}
console.log(add(7, 9))
output:
[Arguments] { '0': 7, '1': 9 }
16
也可以個別取得引數:
- 第一個引數
arguments[0]
- 第二個引數
arguments[1]
function add(a, b){
console.log(arguments[0])
console.log(arguments[1])
return a + b
}
console.log(add(7, 9))
output:
7
9
16
因此,也可以不寫參數
在 function 中,也可以不寫參數,直接用「取得引數」的方式做為回傳值 return arguments[0] + arguments[1]
(此方式會用到的機率很低,通常還是會直接把參數寫好)
function add(){
return arguments[0] + arguments[1]
}
console.log(add(7, 9))
// output: 16
arguments 是一個「物件」
因為印出來的 arguments 是用「大括號」包起來的,所以 arguments 是一個「物件」
arguments 不是一個「陣列」,因此,用 Array.isArray(arguments)
來看 arguments
是不是一個陣列,就會回傳 false
function add(a, b){
console.log(arguments)
console.log(Array.isArray(arguments))
return a + b
}
console.log(add(7, 9))
output:
[Arguments] { '0': 7, '1': 9 }
false
16
arguments 是一個類陣列(Array
-like)物件
參考資料 Arguments 物件
但是,arguments 這種形式的物件(key 的值是 0, 1, 2...),就跟陣列沒兩樣,因為我同樣可以用 arguments[0]
讀取到第一個引數
其實正確是要用單引號把 key 包起來: arguments['0']
但是因為「number 的 0」和「string 的 '0'
」是沒有區別的,所以也可以直接寫 arguments[0]
這就是讀取物件值的第二種方式:用中括號來讀取 key 的值(如果 key 是字串,就一定要用單引號把 key 包起來)
var a = {
name: 'Harry'
}
console.log(a['name'])
arguments.length
共有幾個引數
正常的物件,是沒辦法使用 物件名稱.length
來知道物件長度的
但是,因為 arguments 是一個類陣列(Array-like)物件,所以才可以用 arguments.length
來知道總共傳進了幾個引數
function add(a, b){
console.log(arguments.length)
console.log(Array.isArray(arguments))
return a + b
}
console.log(add(7, 9))
output:
2
false
16
為什麼 arguments 是一個物件,而不能是一個 array?
arguments 有一些屬性像是 arguments.callee
和 arguments.caller
,可以印出:是誰在 call 這個 function 的
要印出「是誰在 call 這個 function 的」,就需要帶有一些資訊,這時就需要用到「物件」的格式來儲存資料(用 value 來儲存每個 key 帶有的資訊)
如果是 array 的格式的話,根本沒辦法做到「儲存每個 key 帶有的資訊」這件事
使用 function 時的注意事項
關於這個主題,如果想要了解的更深入、更複雜,可以參考:深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?
function 傳參數的運作機制
情況一:當變數的型別是 primitive 時(number, string, boolean),改 function 裡面的不會影響到 function 外的
用一個 function 交換兩個變數:
因為是要交換兩個值,所以一定要有第三個變數 temp
,用來把 a 的值存起來
接著,宣告兩個變數 number1
、number2
,先把兩個變數的值印出來
執行 swap
函式後,再一次把兩個變數的值印出來
function swap(a, b){
var temp = a
a = b
b = temp
}
var number1 = 50
var number2 = 100
console.log(number1, number2)
swap(number1, number2) // 執行 swap 函式
console.log(number1, number2)
output:
50 100
50 100
結果會發現,兩個變數(number1, number2)的值並沒有交換!
在 swap
函式裡面,再把 a, b 印出來看看 console.log('a, b:', a, b)
會發現:
- 一開始的 number1, number2 是 50, 100
- 經過
swap
函式的 a, b 確實有交換:是 100, 50 - 但是 number1, number2 的值並沒有改變:依然還是 50, 100
function swap(a, b){
var temp = a
a = b
b = temp
console.log('a, b:', a, b)
}
var number1 = 50
var number2 = 100
console.log(number1, number2)
swap(number1, number2) // 執行 swap 函式
console.log(number1, number2)
output:
50 100
a, b: 100 50
50 100
原因
在傳入引數時:
把 number1, number2 傳進去 swap
函式時,其實是「先複製一份」
function swap(a, b) {
/*
會先複製一份(可以想成是這樣,但實際上不是這樣的)
var a = number1
var b = number2
*/
var temp = a
a = b
b = temp
console.log('a, b:', a, b)
}
var number1 = 50
var number2 = 100
console.log(number1, number2)
swap(number1, number2) // 執行 swap 函式
console.log(number1, number2)
因為變數 number1, number2 的型別是 number(屬於 primitive 型別),因此變數存的是「值」,每個變數都會是獨立的,都會在不同的記憶體位置上
既然 a, b 是由 number1, number2 複製來的,因此型別也都會是 number,那麼「a, b, number1, number2」就會在四個完全不一樣的記憶體位置上
「a 和 number1」、「b 和 number2」只是「值一樣」而已,但是卻是「完全不同的變數」(都在不同的記憶體位置上)
所以,在 function 裡面的 a, b 的確是改變了(值交換了),但是這並不會影響到「分別在別的記憶體位置的 number1, number2」的值
情況二:當變數的型別是 object 時(改 function 裡面的也會改到 function 外的)
修改屬性,改 function 裡面的也會改到 function 外的
有一個 addValue
函式,會傳一個 object 進去
- 在
addValue
function 裡面,會讓obj.number
+1 - 在 function 外面,有一個 object 叫做
a = {number: 5}
- 接著,我執行
addValue
函式,把 a 傳進去 - 最後,把 a 印出來
思考一下,a.number
會跟著 +1 嗎?
function addValue(obj){
obj.number++
return 1
}
var a = {
number: 5
}
addValue(a)
console.log(a)
output:
{ number: 6 }
結果會發現,物件 a 也被改到了!
原因是:
詳細說明可參考 讓兩個物件相等:指向同一個記憶體位置(改一個,也同時改到另一個)
在把 a 傳進 addValue
函式時,也會先複製一份,可以想成是:
在 addValue
函式裡面加上這句 var obj = a
因為物件存的是「記憶體位置」,所以複製一份之後,兩個物件(obj
和 a
)還是會指向「同一個記憶體位置」
所以,當我修改物件 obj
時,也會同時改到物件 a
function addValue(obj){
/*
先把「物件 a」複製一份
var obj = a
*/
obj.number++
return 1
}
var a = {
number: 5
}
addValue(a)
console.log(a)
新增屬性,改 function 裡面的也會改到 function 外的
function addValue(obj) {
/*
先把「物件 a」複製一份
var obj = a
*/
obj.test = 30
return 1
}
var a = {
number: 5
}
addValue(a)
console.log(a)
output:
{ number: 5, test: 30 }
結果是:物件 a 也跟著新增 test
屬性了!
讓變數 obj
等於一個新的物件(改 function 裡面的,不會影響到 function 外的)
這時,我讓變數 obj
等於一個新的物件: obj = {number: 100}
思考一下,最後印出來的物件 a,會是 {number: 5}
還是 {number: 100}
呢?
function addValue(obj) {
/*
先把「物件 a」複製一份
var obj = a
*/
obj = {
number: 100
}
return 1
}
var a = {
number: 5
}
addValue(a)
console.log(a)
output:
{ number: 5 }
結果是:物件 a
依然還是原本的 { number: 5 }
原因是:
因為我「用 =
」讓變數 obj
等於一個新的物件,變數 obj
就會指向另一個新的記憶體位置(斷開原本跟物件 a 的連結)
兩個物件(obj
和 a
)不再是同一個記憶體位置了,所以修改 obj
根本不會去改到 a
重點總結
以上這些就是在使用 function 時,要注意的事情:
在 function 裡面修改 object(物件、陣列)時,有可能也會改到 function 外的物件、陣列
但如果是在 function 裡面修改 primitive 型別的變數,就絕對不會影響到外面的
return 不 return,有差嗎?
在 function 裡面,
return 的作用是什麼?什麼時候要用?什麼時候不用?
可以把 function 簡單分成兩類:
1. 只是呼叫這個 function 而已,它不會回傳任何值(我不需要知道它的結果)--> 有沒有 return 都沒差
例如:
function greeting(name){
console.log('hello', name)
}
greeting('Harry')
// output: hello Harry
當然,如果我想要回傳,也是可以加上一個 return
,例如下面的 return 1
,但是這不會有任何影響,output 還是一樣是 hello Harry
function greeting(name){
console.log('hello', name)
return 1
}
greeting('Harry')
如果我在 function 裡面沒有特別寫 return 的話,預設會是 return undefined
像是這樣:
function greeting(name){
console.log('hello', name)
return undefined
}
greeting('Harry')
要怎麼看到這個 return undefined
的結果呢?
- 在 function 裡面不執行任何事情
- 宣告一個變數
var a = greeting('Harry')
,a 會去接收「greeting
這個 function 的回傳值」,也就是 return 的值 - 最後把 a 印出來
結果就是:a 會是 undefined
function greeting(name){
// console.log('hello', name)
}
var a = greeting('Harry')
console.log(a)
// output: undefined
自己 return 其他東西
function greeting(name){
console.log('hello', name)
return 'I am a'
}
var a = greeting('Harry')
console.log(a)
output:
hello Harry
I am a
執行順序是:
- 執行函式
greeting('Harry')
- 先執行
console.log('hello', name)
,印出 hello Harry - 再執行
return 'I am a'
,這時,a 就會等於我要 return 的值 'I am a'
- 印出 a
2. 需要 function 做運算後,回傳運算完的值(我需要知道它的結果)--> 就要在 function 裡面加上 return
例如:
這個 double
函式,我想要知道一個數乘以 2 的結果是多少
如果我只有寫 double(8)
,是不會印出任何東西的
function double(x){
return x * 2
}
double(8)
宣告一個變數 var result = double(8)
,這個 result 就會是「double
函式 return 的值」
再把 result 印出來
function double(x){
return x * 2
}
var result = double(8)
console.log(result)
// output: 16
在 function 執行完 return 後,就不會繼續往下執行 return 後面的程式碼了
例如:
我把 console.log('hello')
放在 return 前面,就會印出 hello
function double(x){
console.log('hello')
return x * 2
}
var result = double(8)
console.log(result)
output:
hello
16
但是,如果把 console.log('hello')
放在 return 後面,執行完 return 後,就會跳出 function(不會執行到 console.log('hello')
這句),然後接著執行第六行
function double(x){
return x * 2
console.log('hello')
}
var result = double(8)
console.log(result)
output:
16
return 多個值,就用 +
連接
例如: return str[i] + ' ' + i
function position(str){
for(let i=0; i<str.length; i++){
if(str[i] >= 'A' && str[i] <= 'Z'){
return str[i] + ' ' + i
}
}
return -1
}
console.log(position("abCD"))
// output: C 2