Web開發學習筆記10 — 預設參數、Spread Operator、Rest Operator、解構賦值、陣列方法的練習筆記


Posted by Teagan Hsu on 2020-12-24

預設參數(Default parameters)

我們的參數有(x, y)兩個,但如果我們只輸入x的參數,則y被定義為undefined,透過強制轉型undefined會被轉成NaN:

let add = (x, y) => {
    return x + y;
}

add(1, 2);       //3
add(1);          //NaN

要解決上述的問題,我們可以預先設定預設參數:

舊方法:透過if語句去判斷y的狀態,並指定預設值

let add = (x, y) => {
    y = (y !== undefined) ? y : 2;
    return x + y;
}

add(1, 2);       //3
add(1);          //3

新方法:ES2015增加了預設參數(Default parameters),可以在函式參數中直接指定預設值(目前IE沒有支援)

let add = (x, y = 2) => {
    return x + y;
}

add(1, 2);       //3
add(1);          //3

順序的重要性

預設參數的順序會影響到函式的運作結果。
我們將greeting的預設值指定為'Hi':

let greet = (greeting = 'Hi', name) => {
    console.log(`${greeting}! ${name}.`)
}

greet('Amy');       //Amy! undefined.

為何會出現這種結果,是因為greeting(參數1)被指定成'Amy',所以name(參數2)其實是沒有被指定預設值的undefined
這告訴我們想要預設參數的值應該排在順序後面會比較佳,改寫一下後就可順利跑出預想的結果:

let greet = (name, greeting = 'Hi') => {
    console.log(`${greeting}! ${name}.`)
}

greet('Amy');       //Hi! Amy.

Evaluated at call time

預設函數有一個特性就是Evaluated at call time,當我們每次呼叫函式時都會創立一個新的物件。

function newArray(num, arr = []) {
    arr.push(num);
    return arr;
}

newArray(1);        //[1]
newArray(2);        //[2] 不是[1, 2]而是[2]

初始參數可用於之後的默認參數

參數verbs可以被用到參數question裡面,以下範例:

function sentence(name, verbs, question = `Do you ${verbs} me?`){
return (`${name}, I ${verbs} you. ${question}`);
}

sentence('Ansel', 'like');
//"Ansel, I like you. Do you like me?"

sentence('Ansel', 'like', 'How about you?');
//"Ansel, I like you. How about you?"

展開運算子(Spread Operator)

展開運算子長得像是...(Ellipsis),也被稱作three dots。
語法

//用在呼叫函式時:
myFunction(...iterableObj); 


//用在陣列迭代或字串時:
[...iterableObj, '4', 'five', 6];


//用在物件迭代時(ECMAScript 2018新增):
let objClone = { ...obj };

在呼叫函式時展開

將可iterable的(陣列,字串等)展開為參數列表:

let arr = [0, 2];
function add(x, y) {
    return x + y
};
add(...arr);      //2

用apply也可以達到一樣的效果:

add.apply(null, arr);

使用new建構函數時,不能直接使用陣列和apply()。要解決這個問題,我們就可以使用spread

let date = [1998, 0, 1];    //1998年1月1號
let newDate = new Date(...date);
newDate;      //Thu Jan 01 1998 00:00:00 GMT+0800 (台北標準時間)

在陣列迭代中使用spread

如果沒有spread,我們通常需要使用push(), splice(), concat()等來結合陣列,但有了spread程式就可以寫得更加簡潔。以下有兩個範例:
使用spread

//範例一
let arr = ['apple', 'orange', 'banana'];
let fruits = ['peach', ...arr, 'pear'];
fruits;         //["peach", "apple", "orange", "banana", "pear"]

//範例二
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr1 = [...arr1, ...arr2];   //[1, 2, 3, 4, 5, 6]

改用push()和unshift()、concat()、splice()也可以達到上面範例的結果。注意push()、unshift()、pop()等不能串接在一起,需寫成function形式回傳值:

//範例一
let array = ['apple', 'orange', 'banana'];
function combine(arr) {
arr.push('pear');
arr.unshift('peach');
return arr;
}
combine(array);   //["peach", "apple", "orange", "banana", "pear"]

//或是

function combine(arr){
arr.splice(0, 0, 'peach');
arr.push('pear');
return arr;
}
combine(array);   //["peach", "apple", "orange", "banana", "pear"]

//範例二
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr1 = arr1.concat(arr2);   //[1, 2, 3, 4, 5, 6]

spread還能幫助我們iterable轉變為陣列,像是以下範例:

let str = 'Hello!';
let arr = [...str];      //["H", "e", "l", "l", "o", "!"]

在物件迭代中使用spread

用現存的陣列建立一個新陣列,從一個陣列中展開元素到另一個新陣列中。
淺拷貝(Shallow-cloning):

let movie = {
        title: 'The Godfather',
        score: 92,
        year: 1972
    }
let newMovieArray = {...movie};

newMovieArray; 
//{title: "The Godfather", score: 92, year: 1972}

具有覆蓋性
兩組陣列中有重複的屬性score,會根據newMovie參數的順序影響到movie1.score覆蓋movie2.scoremovie2.score覆蓋到movie1.score。在以下範例中,由於...movie2順序在後,所以會蓋過...movie1裡的score,最後結果就是score: 88了!

let movie1 = {
title: 'The Godfather',
        score: 92,
        year: 1972
}

let movie2 = {
movieName: 'Joker',
        score: 88,
        isuueYear: 2019}

let newMovie = {
...movie1, ...movie2};

newMovie;
//{title: "The Godfather", score: 88, year: 1972, movieName: "Joker", isuueYear: 2019}

剩餘運算子(Rest Operator)

長得跟展開運算子一樣是...(three dots),但功能不一樣,剩餘運算子允許我們把一個不確定數量的參數表示為一個陣列。
語法

function f(a, b, ...theArgs) {
  // ...
}

傳入參數定義,又被稱作剩餘參數(Rest parameters)。我們可以把...nums看作是一個「尚未確定的參數值們」。像是以下的範例:

function sum(...nums) {
    return nums.reduce((acc, current) => {
        return acc + current
    })
}
sum(1, 2);    //3
sum(1, 3, 5, 100);    //109

需要注意的只有位於最後的參數,能使用剩餘參數:

function raceResult(gold, silver, ...theOthers) {
    console.log(`Gold medal goes to ${gold}`);
    console.log(`Silver medal goes to ${silver}`);
    console.log(`Thanks to ${theOthers}`)
}

//傳入參數
raceResult('Tom', 'Tammy', 'Amy', 'Alice');
//Gold medal goes to Tom
//Silver medal goes to Tammy
//Thanks to Amy,Alice

如果把剩餘參數移到最前面,結果會跳出Rest parameter must be last formal parameter

function raceResult(...theOthers, gold, silver) {
    console.log(`Gold medal goes to ${gold}`);
    console.log(`Silver medal goes to ${silver}`);
    console.log(`Thanks to ${theOthers}`)
}

raceResult('Tom', 'Tammy', 'Amy', 'Alice');
//Uncaught SyntaxError: Rest parameter must be last formal parameter

剩餘運算子除了傳入參數的功能外,還有一個功能是「解構賦值」。簡單來說解構賦值就是將「陣列對應到陣列」、「物件對應到物件」,像是鏡子反射一樣的改念,以下用一個簡單的範例說明:

let[x, y] = [1, 2]
let { a, b } = { a: 3, b: 4 }

console.log(x);      //1
console.log(y);      //2
console.log(a);      //3
console.log(b);      //4

好那麼現在我們來看看把剩餘運算子運用到解構賦職中:

let [x, ...y] = [1, 2, 3, 4, 5];
let { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4, e: 5 }

console.log(x);        //1
console.log(y);        //[2, 3, 4, 5]
console.log(a);        //1
console.log(b);        //2
console.log(rest)      //{c: 3, d: 4, e: 5}

當我們最後傳的參數與原本設定的參數數量不一定時,會發生以下結果:

let [x, y, ...z] = [1];
console.log(x);       //1
console.log(y);       //undefined
console.log(z);       //[] 剩餘運算子會回傳空字串

let { a, b, ...rest } = { a: 1 };
console.log(a);       //1
console.log(b);       //undefined
console.log(rest);    //{} 剩餘運算子會回傳空物件

剩餘運算子也可以寫成以下範例這種的解構賦職:

function sum(x, ...[y, z]) {
    return x + y + z
}
sum(1, 2)            //NaN (參數z沒有被傳入,被視為undefined)
sum(1, 2, 3)         //6
sum(1, 2, 3, 4, 4)   //6   (第四、五個參數本來就不存在)

剩餘參數是真正的陣列,而Arguments物件(The arguments object)不是。所以當我們使用陣列方法,如filter()map()reduce()等可能會造成以下情況:

//Use Rest parameters
function numberFilter(...nums) {
    let result = nums.filter((m) => (m > 50));
    return result;
}

numberFilter(1, 2, 100);       //[100]



//Use The arguments object
function numberFilter(){
let result = arguments.filter((m) => (m > 50));
    return result;
}
numberFilter(1, 2, 100);      //Uncaught TypeError: arguments.filter is not a function at numberFilter

如果我們要在arguments後使用陣列方法,必須先將arguments變成真正的陣列:

function numberFilter() {
    let arg = Array.from(arguments)
    let shortArg = arg.filter((m) => (m > 50));
    return shortArg;
}
numberFilter(1, 2, 100); //[100]

解構賦值(Destructuring assignment)

剛剛在剩餘運算子那邊有提到解構賦值,這邊再做一個簡單的範例與介紹。

Destructuring Arrays

const id = ['475638', '327483', '348793', '234894', '094387', '734838']
//平常我們要提取某一元素,我們可能會使用[0]、[1]等:
let id1 = id[0]
let id2 = id[1]
let id3 = id[2]

//如果改用解構賦值語法會比較簡潔:
let [id1, id2, id3] = id;

//我們也可以用剩餘運算子提取剩下的元素們:
let [id1, id2, id3, ...theOthers] = id;
theOthers;     //["234894", "094387", "734838"]

Destructuring Objects

const user1 = {
    firstName: 'Wu',
    lastName: 'Tommy',
    email: 'tommy492942094292875@gmail.com',
    born: '1982',
    city: 'Taipei'
}

//平常我們要提取某一值,我們可能會使用以下方法:
let firstName = user1.firstName;
let email = user1.email;

//但當我們有太多值要提取時,使用解構賦值可以讓程式簡潔很多:
let { firstName, email } = user1;

如果想在解構賦值時順便改變數名稱,我們可以這樣寫:

let { born: birthYear } = user1;

born;          //Uncaught ReferenceError: born is not defined
birthYear;     //"1982"

假設我們想順便增加一個變數,並幫它加上預設參數,可以使用等號:

let { age = 'N/A' } = user1
age;      //'N/A'

Destructuring Params

範例一:假設我們要寫一個可以printfullName的函式:

const user1 = {
    firstName: 'Wu',
    lastName: 'Tommy',
    city: 'Taipei'
}

//一種方法是這樣寫:
function fullName(user) {
    return `${user1.lastName} ${user1.firstName}`;
}

//運用Destructuring Params可以寫成:
function fullName(user) {
    let { firstName, lastName } = user1;
    return `${lastName} ${firstName}`
}

//或是我們不需經過整個user1,只想提取`firstName`和`lastName`:
function fullName({ firstName, lastName }) {
    return `${firstName} ${lastName}`
}

範例二:更簡短的寫法

let allMovie = [{
        title: 'The Godfather',
        score: 92,
        year: 1972
    }, {
        title: 'Joker',
        score: 85,
        year: 2019
    },
    {
        title: 'Forrest Gump',
        score: 88,
        year: 1994
    }
]

//假設我們想return成:The Godfather (1972) is rated 92
//平常我們可能會寫成
allMovie.map((movie)=>{
return `${movie.title} (${movie.year}) is rated ${movie.score}`})

//用Destructuring Params則可以更縮短程式碼
allMovie.map(({ title, year, score }) => {
    return `${title} (${year}) is rated ${score} `
})

陣列方法的練習筆記

合併陣列方法

方法一:使用push()/concat()/unshift()+apply()
注意concat()不會改變原本的陣列,所以要之後要調用需另存變數。

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

Array.prototype.push.apply(arr1, arr2);
arr1;       //[1, 2, 3, 4, 5, 6]

方法二:使用push()/concat()/unshift()+apply()

arr1.push.apply(arr1, arr2);
arr1;       //[1, 2, 3, 4, 5, 6]

方法三:使用spread

arr1 = [...arr1, ...arr2];
arr1;       //[1, 2, 3, 4, 5, 6]

印出陣列1-100的方法

方法一:使用from()

Array.from(Array(100).keys()).map((x) => x + 1);
Array.from(Array(100), (_, x) => x + 1)
Array.from({ length: 100 }, (_, x) => (x + 1));
Array.from({ length: 100 }).map((_, x) => x + 1);

方法二:使用spread

[...Array(100).keys()].map((x) => x + 1);
[...Array(100)].map((_, x) => x + 1);

方法三:使用fill()

Array(100).fill().map((_, x) => x + 1);

object轉換成array的方法:

let obj = { '0': 1, '2': 2, '3': 0, '4': 3, '5': 1 };
covert to
[[0, 1], [2, 2], [3, 0], [4, 3], [5, 1]]

方法一:使用keys()與map()

Object.keys(obj).map((key) => { return [key, obj[key]] })

方法二:使用entries()與map()

Object.entries(obj).map((key, value) => {
    return [Number(key), value]
})

方法三:使用from()與keys()

Array.from(Object.keys(obj), (key, value) => {
    return [Number(key), obj[key]]
})

從物件陣列中取出單一value成新陣列

let allMovie = [{
        title: 'The Godfather',
        score: 92,
        year: 1972
    }, {
        title: 'Joker',
        score: 85,
        year: 2019
    },
    {
        title: 'Forrest Gump',
        score: 88,
        year: 1994
    }
]

allMovie.reduce((accumulator, currentValue) => {
    let result = accumulator.concat(currentValue.title);
    return result;
}, [])

//["The Godfather", "Joker", "Forrest Gump"]

Default parameters - MDN
Rest parameters - MDN
How to convert an Object {} to an Array [] of key-value pairs in JavaScript - stack overflow
Find object by id in an array of JavaScript objects - stack overflow
How to create an array containing 1…N - stack overflow


#Default parameters #Spread Operator #Rest Operator #Destructuring assignment #Array Methods







Related Posts

[Note]  JS: Scroll events

[Note] JS: Scroll events

《鳥哥 Linux 私房菜:基礎篇》Chapter 06 - Linux 的檔案與目錄管

《鳥哥 Linux 私房菜:基礎篇》Chapter 06 - Linux 的檔案與目錄管

 [Leetcode in Java] 541. Reverse String II

[Leetcode in Java] 541. Reverse String II


Comments