隨著程式碼的內容除漸變多,許多重複的結構會顯得格外的笨拙而且不易維護。
函式的使用就相當的重要。
# 宣告函式
當開始重運某的流程,而複製使用了極為相似的程式碼,如果發現當中只有值是不同的,就可以考慮將那些片段封裝為函式,舉個例子:
let max1 = a > b ? a : b; | |
let max2 = x > y ? x : y; |
將流程中不同的引用數值設計為 參數 (Parameter):
function max(n , m){ | |
return n > m ? n : m; | |
} |
在 JS 世界哩,宣告的方法是使用 function
關鍵字,若要回傳值就可用 return
回傳值。若沒有回傳值的話,預設就會回復 undefined
這麼一來,原先的片段就可以改成:
let max1 = max(a,b); | |
let max2 = max(x,y); |
順帶一提,陳述句是可以不寫分號的,換行自動就會做為結束的依據,但不是個很好的事情。例如:
function foo(){ | |
return | |
{ | |
x: 10, | |
y: 20 | |
} | |
} | |
console.log(foo()); | |
undefined | |
undefined |
在這裡函式沒有報錯。但是 return
換行後會被視為結束,所以,就變成了 return
沒有回傳值,導致回傳成 undefined
。
請留意 {}
的風格,在撰寫 JS 的程式時,推薦 {}
的對其採右上、左下的風格。
如果習慣把 {}
縮在同一排的請注意一下,在 JS 的世界裡不要這麼做!!
不是風格問題,而是避免被誤當成陳述句結束。
若對上述程式碼做出修正,就會像這樣:
function foo(){ | |
return { | |
x: 10, | |
y: 20 | |
} | |
} | |
console.log(foo()); | |
{ x: 10, y: 20 } | |
undefined |
函式有時也會呼叫自己本身,我們稱之為遞迴。例如要求最大公因數可以寫成:
function gcd( m , n ){ | |
if(n == 0) | |
return m; | |
return gcd( n , m % n ); | |
} | |
let m = 1000; | |
let n = 50; | |
let r = gcd(m,n); | |
if(r ===1) { | |
console.log('兩者互質'); | |
} | |
else { | |
console.log(`最大公因數為: `,r); | |
} |
# 參數與引數
# 預設參數
在 ES6 以後的版本可以使用預設引數,有限度的模仿函式重載。
function account(name, number, balance = 100){ | |
return {name, number, balance}; | |
} | |
console.log(('Zrn','123-4567')); | |
// 顯示 {name: `Zrn`, number: '123-4567', balance: 100} | |
console.log(account('Zzz','012-1234',10000)); | |
// 顯示 {name: `Zzz`, number: '012-1234', balance: 1000} |
在沒有指定第三個引數時,會使用預設的引數。
順帶一提, return { name,number,balance }
這種寫法是 ES6 版本新增的
之前的版本會寫成: return { name: name,number: number ,balance: balance }
# 不定長度引數
ES6 以後,可以用 ...參數名
,表示該參數接受不定長度的引數。
傳入的函式引數會被收集在一個陣列裡面,因為是陣列的因素,使用 for...of 語法是最簡便的方式。
function sum(...numbers){ | |
let total = 0; | |
for( let x of numbers ) | |
total +=x; | |
return total; | |
} | |
console.log(sum(1)); // 顯示 1 | |
console.log(sum(1,2,3,4,5));// 顯示 15 |
在 ES5 以前的版本,必須使用自動形成的 arguments
,
使用參照類陣列的方式,存著函數全部的引數。
function sum(){ | |
var sum = 0; | |
for( var i = 0;i < arguments.length; i++ ) | |
sum += arguments[i]; | |
return sum; | |
} | |
console.log(sum(1)); // 顯示 1 | |
console.log(sum(1,2,3,4,5));// 顯示 15 |
# 選取物件
隨著參數的個數越多,而且每個參數皆有重要意義時,可以使用選取物件的技巧。
function ajax(url, option){ | |
let realOption = { | |
method: option.method || 'GET' | |
contents: option.contents ||'', | |
dataType: option.dataType || 'text/plain', | |
.... | |
}; | |
console.log('請求', url ); | |
console.log('設定', realOption); | |
} | |
ajax('https://zrn-code.github.io',{ | |
method: 'POST', | |
contents: 'javascript' | |
}); |
試圖存取物件上不存在的特行時,會回傳 undefined
運用 ||
可以對沒有設定引數的參數產生預設值。
# 運用一級函式
# filter()
函式
JS 中已有的預設函式,可以過濾符合條件的物件
// 撰寫時,可省略 | |
function filter(arr,predicate) { | |
let result = []; | |
for(let x of arr) | |
if(predicate(x)) | |
result.push(x); | |
return result; | |
} | |
function morethan6(x){ | |
return x.length > 6; | |
} | |
function lessthan5(x){ | |
return x.length < 5; | |
} | |
function hasi(x){ | |
return x.includes('i'); | |
} | |
let arr = ['Zrn','Hello','GitHub']; | |
console.log('大於6: ',filter(arr,morethan6)); | |
console.log('小於5: ',filter(arr,lessthan5)); | |
console.log('有i的: ',filter(arr,hasi)); |
# map()
函式
JS 中已有的預設函式,可以回傳相對應的物件
// 撰寫時,可省略 | |
function map(arr,mapper){ | |
let result = []; | |
for(let x of arr) | |
result.push(mapper(x)); | |
return result; | |
} | |
function toUpper(x){ | |
return x.toUpperCase(); | |
} | |
function length(x){ | |
return x.length; | |
} | |
let arr = ['Zrn','Hello','GitHub']; | |
console.log(map(arr,toUpper); | |
console.log(map(arr,length); |
# sort()
函式
JS 已有的預設函式,可以對陣列的元素做排序
> [ 200, 30, 15, 100 ].sort() | |
[ 15, 30, 100, 200 ] | |
function ascend(n1,n2){ | |
return n1 - n2; | |
} | |
function descend(n1,n2){ | |
return n2 - n1; | |
} | |
let arr = [ 200, 30, 15, 100 ]; | |
console.log(arr.sort(ascend)); // [15,30,100,200] | |
console.log(arr.sort(descend));// [200,100,30,15] |
在 ECMAScript 規範中,沒有定義 sort()
的排序結果必定是穩定的。
# 函式實字與箭頭函式
如果函數體本身只有一句簡單的運算,可以考慮使用函數實字的方法。
let arr = ['Hello','World','Zrn']; | |
let lengs = arr.map(function(name){ | |
return name.length; | |
}); | |
console.log(lengs); // 顯示 [5, 5, 3] | |
let upper = arr.map(function(name){ | |
return name.toUpperCase(); | |
}); | |
console.log(upper); // 顯示 ['HELLO','WORLD','ZRN'] |
實字補充
let n = 10; // 數字實字 | |
let arr = [1, 2, 3]; // 陣列實字 | |
let obj = {x: 10, y: 20}; // 物件實字 | |
let func = function( p ){ // 函數實字 | |
return p * 2; | |
} |
函式實字的通常會以匿名函式建立,但有時會為了配合遞迴附加名稱。例如:
let gcd = function g(n1, n2){ | |
return n2 != 0 ? g( n2 , n1 % n2 ) : n1; | |
} | |
console.log(gcd(10,5)); // 顯示 5 | |
//gcd 可以被省略 | |
console.log( | |
(function g(n1, n2){ | |
return n2 != 0 ? g( n2 , n1 % n2 ) : n1; | |
})(10,5) | |
); // 顯示 5 |
這種建立後立即呼叫的方式叫做: 立即呼叫運算式。
函式實字主要用來封裝一段小程式碼,例如:
> .editor | |
//Entering editor mode (^D to finish,^C to cancel) | |
let arr = [1,2,5,2,5,6,9]; | |
arr.filter(function(n){ | |
return n % 2 === 0; | |
}) | |
.map(function(n){ | |
return n*10; | |
}); | |
[ 20 , 20 , 60 ] | |
> |
但又因為 function 太長,可讀性很差,為了改善這問題,就產生了箭頭函式:
> .editor | |
//Entering editor mode (^D to finish,^C to cancel) | |
let arr = [1,2,5,2,5,6,9]; | |
arr.filter( n => n % 2 === 0) | |
.map(n => n * 10 ); | |
[ 20 , 20 , 60 ] | |
> |
在只有一行的運算,會直接回傳右側運算的結果。
如果要搭配換行,就加上 return
的使用即可。
let plus = (a, b) => a +b; | |
let minus = (a, b) =>{ | |
return a - b; | |
} |
如果函式體很長的話,極不推薦使用箭頭函式。
# 模板字串與標記模板
ES6 以後的版本支援模板字串,可以使用「`」 來建立字串。
> .editor | |
//Entering editor mode (^D to finish,^C to cancel) | |
let html = `<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Hello,JS!!</title> | |
</head> | |
<body> | |
Hello,JS!! | |
</body> | |
</html> | |
` | |
console.log(html); | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Hello,JS!!</title> | |
</head> | |
<body> | |
Hello!!JS!! | |
</body> | |
</html> | |
undefined | |
> |
從此可以看出,字串換行和空白等,在字串模板中都有保留。
其中也可用 ${}
進行變數運算,將其結果當作字串,讓 HTML
模板更有彈性。
let title = `Hello,JS!!`; | |
let message = `Hello!!JS!!`; | |
let html = `<!DOCTYPE html> | |
<html> | |
<head> | |
<title>${title}</title> | |
</head> | |
<body> | |
${message} | |
</body> | |
</html> | |
` |
運行後的 html 就會等於:
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Hello,JS!!</title> | |
</head> | |
<body> | |
Hello!!JS!! | |
</body> | |
</html> |
標記模板其實是特殊形式的函式呼叫,若有個函式 f()
,底下的運算結果為 f('10+20=30')
。
let a = 10; | |
let b = 20; | |
f(` ${a} + ${b} = ${a + b}`); |
如果寫成這種形式:
f`${a} + ${b} = ${a + b}` |
最後運算的值,最後會以陣列的行事作為引數函式。而此陣列的記錄方式為:
f(['', ' + ', '=',''],10,20,30]) |
若模板中的數量無法事先決定則可以使用
function f(strings, ...values){ | |
// 函式體 | |
} |
所以標記模板可以使 ${}
收集的值入陣列內。
ES6 本身就有標準函式可以進行標記模板的特殊函式運算,例如 String.raw 函是可以讓標記模板行式呼叫,字串會被當作原始字串,不會進行轉譯。
console.log(String.raw`ABC\nEFG`); | |
// 顯示 ABC\nEFG |
陣列有 raw
屬性可以取得原始字串。