0%

[JS] JavaScript ES6 常用語法筆記

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

變數宣告

varlet 的區別

  1. 是否會提升
    當我們分別用 varlet 並在宣告變數之前調用變數時,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()