預設參數(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.score
或movie2.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