0%

[JS] JavaScript ES6 常用語法筆記

一直以為有發過這篇,結果竟然沒有!
學習 JavaScript 這麼久了,筆記中當然不能少了 ES6 常用語法囉~

變數宣告

var 與 let 的區別

  1. 是否會提升
    當我們分別用 var 跟 let 並在宣告變數之前調用變數時,var 會顯示 undefined,代表記憶體有預先準備空間;let 則會顯示 not defined,代表記憶體沒有預先準備空間。
  2. 作用域不同
    var 是以 function 做分隔,let 是以 block ( {} ) 做分隔。

const

代表常數,唯讀,不可再次賦值。但是如果用 const 宣告一個物件,可以在之後修改或新增該物件內部的特性,因為物件是傳址(參考)而不是傳值,所以可以這樣做。其他特色與 let 一樣。

迴圈內的 setTimeout()

假如我們希望用 setTimeout 配合迴圈輸出 1~10,而且每次輸出都延遲 10 毫秒:

1
2
3
4
5
for( var i=0; i<10; i++){
setTimeout(function(){
comsole.log(i);
},10);
}

此時迴圈的計數器 i 是用 var 來宣告,則 setTimeout 得到的數字不會是 1~10,而是得到最後的數字 10,為什麼會這樣呢?

因為 JS 非同步的特點,所以 for 迴圈並不會等到 setTimeout() 結束後才繼續,而是一口氣跑完 1~10;又因為 var 的作用域是以 function 做切分,setTimeout() 的 i 會從外層去取值,此時的 i 已經是跑完 for 迴圈的 10 了。

解決方法是改用 let 宣告計數器,這樣 setTimeout 得到的就會是當下迴圈跑的數字。

另一種解法:立即函式 (IIFE)

除了改用 let 宣告以外,也可以用立即函式包住 setTimeout,並將迴圈的 i 作為參數傳入立即函式中,保留當下的 i 一筆筆輸出數字。

1
2
3
4
// 立即函式語法
(function(){
// 做某事
})();
1
2
3
4
5
6
7
8
// 用立即函式包住 setTimeout()
for (var i=0; i<10; i++){
(function(x){
setTimeout(function(){
console.log('這執行第'+ x +'次');
},10);
})(i);
}

展開符號 …

展開符號能夠把陣列與類陣列裡的內容給取出來。以下說明展開符號的幾個用途。

合併數個陣列

在一個新宣告的陣列中,把展開符號 ... 加在要合併的陣列前方,這種合併的方式與使用 concat() 的效果是一樣的。

1
2
3
4
5
let groupA = ['小明', '杰倫', '阿姨'];
let groupB = ['老媽', '老爸'];
let groupAll = [...groupA, ...groupB];
console.log(groupAll);
// ['小明', '杰倫', '阿姨','老媽', '老爸']

複製陣列(淺複製)

陣列與物件一樣,都是傳址(傳參考),所以如果將 B 陣列指向 A陣列,A、B 陣列會被視為同一個實體。如此一來,當我們想要修改 B 陣列時,就會連 A 陣列的內容也修改到。這叫做深複製。

如果只想修改 B 陣列的話,就可以用展開符號來複製 A 陣列,再進行操作。只傳值的複製方式,就是淺複製。

1
2
3
4
let groupA = ['小明', '杰倫', '阿姨'];
let groupB = [...groupA] // 新陣列
groupB.push('阿明');
console.log(groupA); // 不會有阿明

類陣列

有些東西外觀很像陣列,也具有一些陣列的特性,但卻不是真正的陣列,稱為類陣列,例如用 querySelectorAll() 取出來的 NodeList 會以類陣列的形式呈現所有被選取的節點,但它卻不是真正的陣列。

在新的空陣列中,透過展開符號可以複製類陣列中的內容,藉此將類陣列轉為真正的陣列。

1
2
let doms = document.querySelectorAll('li'); // doms 為類陣列
let newDoms = [...doms]; // newDoms 為陣列

另一個類陣列的例子是函式內建的 arguments 物件,arguments 會包含所有傳入函式的參數。

1
2
3
4
5
6
7
8
9
10
11
var originCash = 1000;
function updateEasyCard() {
// let arg = arguments;
let arg = [...arguments]; // 轉成真正的陣列
let sum = arg.reduce(function (accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log('我有 ' + sum + ' 元');
}
updateEasyCard(0); // 我有 1000 元
updateEasyCard(10, 50, 100, 50, 5, 1, 1, 1, 500); // 我有 718 元

其餘參數

如果不確定之後要傳入函式的參數數量,可以在函式的唯一一個或最後一個參數前面加上展開符號(該參數就會變成其餘參數),就能把多傳入的參數值以陣列的形式,容納在其餘參數中。

注意,函式裡只能有一個其餘參數,而且一定要放在最右邊。

參照:其餘參數 - JavaScript | MDN

1
2
3
4
5
6
7
8
9
10
11
function moreMoney(...money) {
console.log(money);
// [100, 200, 300]
}
moreMoney(100, 200, 300);

function moreMoney(name, ...money) {
console.log(name, money);
// "Grete" [100, 200, 300, 400, 500]
}
moreMoney(‘Grete’, 100, 200, 300, 400, 500);

解構賦值

解構賦值語法可以將陣列或物件中的資料取出成獨立變數。
基本上就是把等號右邊的值,套用到等號左邊。

陣列解構賦值

基本用法

在之前,我們如果要修改陣列內的元素,都只能用 = 一個一個直接賦值。

1
2
3
4
let family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
let ming = family[0];
let jay = family[1];
let auntie = family[2];

而現在可以用解構的方式,一次對陣列內所有元素賦值。

1
2
3
4
5
let family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
let [ming, jay, aunt, mom, dad] = family;
// 左邊是新變數,右邊是要賦予的值
console.log(ming, jay, aunt, mom, dad);
// "小明" "杰倫" "阿姨" "老媽" "老爸"

當輸入的變數少於所給的值

如果缺少的變數是陣列最後的值,那就會因為沒有被賦予變數而查不出來。

1
2
3
4
let family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
let [ming, jay, aunt] = family;
console.log(ming, jay, aunt);
// "小明" "杰倫" "阿姨"

如果缺少的變數是在陣列中間的話,缺少的變數要留空,此時其他變數依然可以按照對應的陣列元素賦值。

1
2
3
4
let family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
let [ming, jay, , mom, dad] = family;
console.log(ming, jay, mom, dad);
// "小明" "杰倫" "老媽" "老爸"

當輸入的變數多於所給的值

如果變數的數量多於賦予的值時,多出來的那個變數會被賦予 undefined 的值。

1
2
3
4
let family = ['小明', '杰倫', '阿姨', '老媽', '老爸'];
let [ming, jay, aunt, mom, dad, sis] = family;
console.log(ming, jay, aunt, mom, dad, sis);
// "小明" "杰倫" "阿姨" "老媽" "老爸" undefined

變數交換

使用解構賦值可以即時交換兩個變數的值,不需要透過宣告第三個變數來達成。

1
2
3
4
5
let fruit = 'banana';
let veg = 'tomato';
[fruit, veg] = [veg, fruit]; // 左邊是新的變數名,右邊是交換後的值
console.log(fruit, veg);
// "tomato" "banana"

把字串的字取出並個別宣告變數

1
2
3
4
let str = '吃飽沒';
let [a, b, c] = str;
console.log(a, b, c);
// "吃" "飽" "沒"

預設值

1
2
3
4
let [g = 'Grete', s = 'Steffi'] = ['葛蕾特'];
// g 會被重新賦值,s 會用預設
console.log(g,s);
// "葛蕾特" "Steffi"

物件解構賦值

陣列的解構賦值強調的是順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值。

在物件解構賦值中,冒號前是用來對應物件的屬性名稱,冒號後才是真正建立的變數名稱和被賦值的對象。

參照:[筆記] JavaScript ES6 中的物件解構賦值(object destructuring)

基本用法

從物件中取出一個特性的值,並賦予這個值一個新的變數名稱。

1
2
3
4
5
6
7
8
9
10
let person = {
name: 'Grete',
age: 25,
city: 'Taichung'
}
let {city: address} = person;
// 冒號左邊是物件特性,冒號右邊是新賦予的變數名稱

console.log(address);
// "Taichung"

person.city 的值依然是 'Taichung',而透過解構賦值另外宣告的變數 address 的值也是 'Taichung'。

延伸問題

1
2
3
4
let { name: germanName, fruits: [, apple] } = { name: 'Grete', fruits: ['香蕉', '蘋果', '芭樂'] }
// 右邊的屬性對應左邊的屬性,右邊的值對應左邊的新變數名稱
console.log(germanName, apple);
// "Grete" "蘋果"

左邊 fruits 特性要解構賦值的對象是右邊 fruits 的第二個值,所以在 apple 前面用逗點空了一個位置。

預設值

1
2
3
4
5
let { fruits: apple = '蘋果' } = {};
console.log(apple); // "蘋果"

let { fruits: apple = '蘋果' } = {fruits: '鳳梨'};
console.log(apple); // "鳳梨"

第一個範例中,左邊預設 apple 等於 ‘蘋果’,因為右邊沒有傳新的值,所以會保持 apple 為蘋果。
第二個範例中,右邊傳了新的值,所以 apple 的值就變成鳳梨了。

縮寫

物件縮寫

將一個字串與一個物件,在另一個新物件裡合併。
新物件裡的特性名稱如果跟來源變數一致時,就可以使用縮寫。

1
2
3
4
5
6
7
8
9
10
const Frieza = '弗利沙'
const GinyuTeam = {
Ginyu: '基紐',
Jeice: '吉斯',
burter: '巴特',
}
const newTeam = {
GinyuTeam, // 原始寫法為 GinyuTeam: GinyuTeam
Frieza // 原始寫法為 Frieza: Frieza
}

在 Vue CLI 裡也會時常應用縮寫,例如以下的情境:

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import App from './App'
import router from './router'
// 將套件從 './App' 路徑載入,並使用 App 這個變數名

new Vue({
el: '#app',
router, // 原始寫法為 router: router
template: '<App/>',
components: { App }
});

物件方法縮寫

原始寫法:

1
2
3
4
5
const newTeam = {
showPosture: function () {
console.log('我們是 基紐特戰隊');
}
}

縮寫:

把 : 跟 function 去掉。

1
2
3
4
5
const newTeam = {
showPosture () {
console.log('我們是 基紐特戰隊');
}
}

注意:物件方法縮寫與箭頭函式的結果是不一致的。

縮寫搭配展開與解構賦值

任務:將一個物件賦值到新的變數,並新增一個特性到新物件中,而且不能更改到原物件(=避免參考)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const GinyuTeam = {
Ginyu: {
name: '基紐'
},
Jeice: {
name: '吉斯'
},
burter: {
name: '巴特'
}
};

const newTeam = {...GinyuTeam};
// 將原物件的特性一個個取出(展開),並賦值到新的變數

newTeam.ming = "小明";
console.log(newTeam, GinyuTeam);
// newTeam 有小明,GinyuTeam 維持原樣

箭頭函式

基本寫法

傳統函式長這樣:

1
2
3
4
var callSomeone = function (someone) {
return someone + '吃飯了';
};
console.log(callSomeone('小明'));

改寫成箭頭函式長這樣:

1
2
var callSomeone = someone => someone + '吃飯了';
console.log(callSomeone('小明'));

如何改寫

把參數往前提,把關鍵字 function 刪掉,改成箭頭。
只有一個參數時可以不用小括號包起來,如果沒帶入參數就還是需要小括號。
如果只有單行程式碼而且只是要回傳一個值的情況下,可以省略大括號及 return。
箭頭函式沒有 arguments 物件,所以如果要傳入多個參數值時,可以在箭頭函式的小括號內使用其餘參數。

與傳統函式最不同的地方:this 綁定差異

  • 傳統函式:this 取決於函式呼叫的方式
  • 箭頭函式:this 取決於被定義時所在的物件,「箭頭函式沒有自己的 this」,因此它都是採用其同層級的 this 來作用。

寫 Vue methods 時最好使用傳統函式搭配縮寫,因為使用箭頭函式的話,this 指向的可能不是 Vue 實例。

參照:[筆記] JavaScript ES6 中的箭頭函數(arrow function)及對 this 的影響

善用方式

在進入箭頭函式之前,先把要指向的物件(this)用變數儲存起來。

1
2
3
4
5
6
7
8
9
var antie = {
name: '漂亮阿姨',
callname() {
var vm = this;
setTimeout(() => {
console.log(vm, vm.name);
}, 10);
}
}

字串模板

各位在用 innerHTML 組字串時會不會感到很痛苦呢?我承認我會,因為各種標籤名、文字、加號、單雙引號,全部亂糟糟地都寫在一起,真 D 會讓人眼花啊!

還好,在ES6有新的寫法,可以讓我們好過一點。

基本用法

假設我們要在一個 <ul><li> 裡放入圖片:

1
2
3
4
5
6
// HTML
<ul class="list">
<li>
<img src="logo.png">
</li>
</ul>
1
2
3
4
5
// JS
// 用 const 選取 DOM
const list = document.querySelector('.list')
// 用 const 儲存要放入的圖片路徑
const imgURL = 'logo.png';

接下來要進行渲染至網頁,傳統寫法是這樣:

1
list.innerHTML = '<li><img src='+ imgURL +'></li>'

現在比較好的寫法是:

1
list.innetHTML = `<li><img src=" ${imgURL} "></li>`

新寫法中這個長得像重音符號的東西叫做「反引號」,${} 裡面可放入 JS 程式碼跟變數,支援 ES6 寫法。

組字串不需要使用加號,而選取變數的方法則是把變數名稱包在大括號內,前面再加個 $ 符號,這樣就可以了。

搭配陣列方法使用

假設我們要根據一個陣列裡的資料,一筆筆輸出為 <ul> <li> 列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 陣列
const people = [
{
name: '小明',
friends: 2
},
{
name: '阿姨',
friends: 999
},
{
name: '杰倫',
friends: 0
}
]

let newUl = `
<ul> ${people.map(person =>
`<li>我叫做 ${person.name}</li>`)
.join('')}
</ul>`
  • map() 會把陣列裡所有元素傳進 callback 函式內運算,並且把運算後的元素以陣列的形式回傳。
  • join() 會把陣列裡所有元素以指定的字符連接在一起,這個例子是讓元素間不要有任何字符、逗號。

常用陣列方法

forEach() 與 map()

兩者很像,但 forEach() 不會回傳值,而 map() 會回傳一個新的陣列。
map() 的 callback 函式裡需要使用 return 來回傳運算後的結果,且也需要一個變數來接收 map() 執行後的新陣列。

filter()

filter() 與 find():filter() 回傳的是一個新陣列且裡面包含所有符合條件的元素,而 find() 只會回傳第一個符合條件的元素值。因此,find() 較適合用於搜尋一個特定的值。

every()

所有元素都符合條件才會回傳 true。

some()

部分元素有符合條件就會回傳 true。

reduce()

用於累加或比較值的大小。

累加

reduce() 第一個參數是一個 callback 函式,第二個參數是累加的起始值。

callback 函式的參數中,第一個參數為「前一個元素的值」,如果沒有前一個元素,就使用 reduce() 的第二個參數。

比較值的大小

配合使用 Math.max()。