隨著程式碼的內容除漸變多,許多重複的結構會顯得格外的笨拙而且不易維護。
函式的使用就相當的重要。

# 宣告函式

當開始重運某的流程,而複製使用了極為相似的程式碼,如果發現當中只有值是不同的,就可以考慮將那些片段封裝為函式,舉個例子:

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);

順帶一提,陳述句是可以不寫分號的,換行自動就會做為結束的依據,但不是個很好的事情。例如:

REPL
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 已有的預設函式,可以對陣列的元素做排序

REPL
> [ 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

這種建立後立即呼叫的方式叫做: 立即呼叫運算式

函式實字主要用來封裝一段小程式碼,例如:

REPL
> .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 太長,可讀性很差,為了改善這問題,就產生了箭頭函式:

REPL
> .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 屬性可以取得原始字串。

# 參考資料

JavaScript 技術手冊

更新於 閱讀次數

用實際行動犒賞爆肝的我😀

Zrn Ye LinePay

LinePay