---
# System prepended metadata

title: ECMAScirpt6
tags: ["\U0001F4BB編程/\U0001F5BC前端", "\U0001F4C62022年", "\U0001F5C2Overview"]

---

###### tags: `📆2022年` `💻編程/🖼前端` `🗂Overview`

# ECMAScirpt6
==以下只記錄ES5和ES6差異較大且常用的語法、概念，主要用於學習往後前端框架的預備知識。==

- European Computer Manufacturers Association(ECMA，歐洲計算機制造聯合會)，是評估、開發、認可電信和計算機標準的組織
- ECMA是標準，JavaScript是實現。參考：[[🏖JavaScript_00_Overview#🏖簡介]]
	- 目的是讓所有前端腳本都實現ECMA，但目前只有JavaScript實現ECMA標準，因此ECMAScript ≈ JavaScript
- ECMAScript，ECMAScript 2015，簡稱ECMA或ES(ES6)
	- 高級瀏覽器支持ES6
	- 低級濟覽器主要支持ES5(ES3.1)
- ECMA官網：[https://www.ecma-international.org/]()
- [ES6兼容表](https://kangax.github.io/compat-table/es6/)

## 🏖變量聲明
- 使用`var`聲明變量的缺點：
	1. `var`可重複聲明
	2. `var`無法限制修改
	3. `var`無塊級作用域(如：`{}`、`if`、`for`…)，只有在`function()`中有作用域

### let
- `let`不可重複聲明
	```javascript
	let test01 = 10
	// Uncaught SyntaxError: Identifier 'test01' has already been declared
	let test01 = 20
	```
- `let`有作用域
	```javascript
	// Uncaught ReferenceError: test01 is not defined
	{
		let test01 = 10
	}
	console.log(test01)
	```
- `let`不存在變量提升，變量必須先使用後聲明
	```javascript
	// Uncaught ReferenceError: Cannot access 'test01' before initialization
	console.log(test01)
	let TESE_01 = 10
	```

### const
`const`用於聲明一個只讀的常量，像Java中的`final`，並且一旦聲明必須初始化

- 不可重複聲明
- 有作用域
- 不存在變量提升，變量必須先使用後聲明
- `const`聲明的變量為只讀的常量；如果聲明的是複合的常量，複合常量的值可以修改，但無法再賦值其他複合常量
	```javascript
	// Uncaught TypeError: Assignment to constant variable.
	const TESE_01 = 10
	TESE_01 = 20
	// 如果聲明的是複合的常量，複合常量的值可以修改，但無法再賦值其他複合常量
	// 因為const聲明的變量只是不能修改棧內存的地址，但可以修改引用地址中的內容
	const TEST_02 = {name: "mickey", id: "123"}
	TEST_02.id = 233
	console.log(TEST_02)
	```
- 一旦聲明必須初始化
	```javascript
	// Uncaught SyntaxError: Missing initializer in const declaration
	const test01
	```

## 🏖=> 箭頭函數
### 基本用法
將`function`簡化為`=>`，類似於java中的Lambda表達式，參考：[[💡Java8_01_Lambda#💡基礎語法]]

```javascript
// ES6前寫法
const FUN_01 = function(num){
    return num * num
}
console.log(FUN_01(10))

// ES6簡化1，方法中有多行代碼
const FUN_02 = (num) => {
    return num * num
}
console.log(FUN_02(20))

// ES6簡化2，方法中僅一行代碼，可省略{}和return
// 如果只有一個參數，可省略()
const FUN_03 = num => num * num
console.log(FUN_03(30))
const FUN_04 = (num1, num2) => num1 * num2
console.log(FUN_04(10, 20))

// ES6簡化3，方法無輸入參數
const FUN_05 = () => 10
console.log(FUN_05())

// 直接返回複合常量，可以用()包起，避免歧義造成語法錯誤
const FUN_06 = id => ({name: 'mickey', id: id})
console.log(FUN_06('233'))
```

### this指向問題
- 無法用`=>`作為構造器方法
	```javascript
	// ES6前，function構造器方法
	function constructer01() {}
	const OBJ_01 = new constructer01()
	console.log(OBJ_01)
	
	// ES6，無法用=>作為構造器方法
	// Uncaught TypeError: constructer02 is not a constructor
	constructer02 = () =>{}
	const OBJ_02 = new constructer02()
	console.log(OBJ_02)
	```
- `=>`建立的函數，不是調用對象本身，可參考：[[🏖JavaScript_05_函數#🏖this]]
	```javascript
	// function函數中，this代表調用對象本身
	const OBJ_03 = {
	    fun: function(){
	        console.log(this)
	    },
	}
	OBJ_03.fun(); // {fun: ƒ}
	
	// 沒有調用者默認為window
	const OBJ_04 = function(){
	    console.log(this)
	}
	// 相當於window.OBJ_04()
	OBJ_04(); // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
	
	// =>函數中，this代表window對象，和調用對象無關
	const OBJ_05 = {
	    fun: () => {
	        console.log(this)
	    },
	}
	OBJ_05.fun(); // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
	```
- 箭頭函數的`this`為定義時所在的對象，默認使用父級的`this`，也就是說箭頭函數的`this`是繼承來的，沒有自己的`this`
	```html
	<!DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <meta http-equiv="X-UA-Compatible" content="IE=edge">
	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
	    <title>Document</title>
	    <style>
	        #box{
	            width: 100px;
	            height: 100px;
	            background-color: aqua;
	        }
	        #box.bgcolor{
	            background-color: cadetblue;
	        }
	    </style>
	</head>
	<body>
	    <div id="box"></div>
	    <script>
	        // 箭頭函數的this為定義時所在的對象
	        const myBox = document.getElementById('box')
	        myBox.onclick = function(){
	            setTimeout(() => {
	                console.log(this)
	                this.className = 'bgcolor'
	            }, 3000);
	        }
	    </script>
	</body>
	</html>
	```

## 🏖數組新增的高級方法
- ES5數組常用方法，可參考：[[🏖JavaScript_08_常用對象#🏖Array 數組]]
- 使用方法類似於Java 8中的StreamAPI，可參考：[[💡Java8_02_StreamAPI]]
- 以下只列出常用方法

```javascript
let array01 = [5, 20, 30, 60, 13, 50, 6, 120, 8]
// filter，過濾器，過濾返回false的參數
let array02 = array01.filter(num => num >= 10)
console.log(array02)
// map，映射，返回處理後的參數
let array03 = array02.map(num => num * 0.5)
console.log(array03)
// reduce，匯總，將流中元素反複結合，得到一個值
let total = array03.reduce(
    (preNum, nowNum) => preNum + nowNum,
    0 // 初始值
)
console.log(total)

// 合并使用
let newTotal = array01
	.filter(num => num >= 10)
	.map(num => num * 0.5)
	.reduce(
		(preNum, nowNum) => preNum + nowNum,
		0 // 初始值
	)
console.log(newTotal)
```

## 🏖新增Set、Map數據結構
- ES6中新增Set和Map兩種數據結構，相關用法可參考：[ES6 Map 与 Set - 菜鳥教程](https://www.runoob.com/w3cnote/es6-map-set.html)
	- Set為不可重覆的List，和Java的概念不一樣，可參考[Set - MDN Web Docs - Mozilla](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Set)
	- Map為鍵值對，其中鍵不可重覆，概念類似於Java中的Map
		- `size`屬性，Map總成員數
		- `set(key, value)`，添加鍵值對
		- `get(key)`，取得鍵值對
		- `has(key)`，Map中是否包含指定鍵
		- `delete(key)`，刪除鍵值對
		- `clear()`，清空所有鍵值對
		- `keys()`，返回所有鍵名遍歷器
		- `values()`，返回所有鍵的值遍歷器
		- `entries()`，返回Map中所有成員遍歷器
		- `forEach()`，遍歷Map所有成員

## 🏖字符串新增功能
- 新方法
	- `startWith(str)`，字符串是否以str開頭
	- `endWith(str)`，字符串是否以str結尾
- 模版字符串，Template String
	```javascript
	// 模版字符串
	let value = 'Hello My World' // 變量聲明必須在模版字符串前面
	let templateStr01 = `
	    <h1>Hello World</h1>
	    <b>支持換行<br>
	    <p>支持變量：${value}</p>
	`
	console.log(templateStr01)
	```

## 🏖解構賦值
解構賦值可以方便數組或物件中的資料，可參考：[解構賦值 - mozilla.org](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)

- 數組中成員數左右必須一致
	```javascript
	let list01 = ['a', 'b', 'c']
	// 原始方式取得陣列中的值
	// let str01 = list01[0]
	// let str02 = list01[1]
	// let str03 = list01[2]
	// ES6新方式
	let [str01, str02, str03] = list01
	console.log(str01)
	console.log(str02)
	console.log(str03)
	```
- 物件的屬性名和變量名必須一致
	```javascript
	let obj01 = {
	    name: 'Mickey',
	    id: 123,
	    gender: 'male',
	}
	// 原始方式取得物件中的值
	// let name = obj01.name
	// let id = obj01.id
	// let gender = obj01.gender
	// ES6新方式
	let {name, id, gender} = obj01
	console.log(name)
	console.log(id)
	console.log(gender)
	```
- 聲明時必須賦值
	```javascript
	let list01 = ['a', 'b', 'c']
	// 會報錯
	// let [str01, str02, str03]
	// [str01, str02, str03] = list01
	```

### ... 三點運算符
詳細用法可參考：[Spread Operator & Rest Operator(展開與其餘運算符)](https://ithelp.ithome.com.tw/articles/10185496)

- 展開運算符，用於展開數組
	```javascript
	// 展開運算符
	let arr01 = [11, 12, 13]
	let arr02 = [21, 22, 23]
	console.log([...arr01, 7, 8, 9, ...arr02])
	```
- 其餘運算符
	```javascript
	// 後面多余的參數都會放入數組中
	function test02(a, b, ...c){
	    console.log(c) // (5) [53, 54, 55, 65, 57]
	}
	test02(51, 52, 53, 54, 55, 65, 57)
	```

## 🏖Class類的概念
更接近於面向對象的用法
- `constructor`，表示構造方法
- `this`，表示實例對象
- `extends`，表示繼承
- `super`，表示父類構造函數，用於建立父類`this`對象

```javascript
// ES5前，類的使用方式
function Person01(name, age, gender){
    this.name = name
    this.age = age
    this.gender = gender
    this.printName = function(){
        console.log(this.name)
    }
}
let person01 = new Person01('Mickey', '123', 'male')
person01.printName()

// ES6的方式
class Person02{
    // 構造方法，有參
    constructor(name, age, gender){
        this.name = name
        this.age = age
        this.gender = gender
    }
    // 構造方法，無參
    // constructor(){
    //     this.name = 'unknow'
    //     this.age = 'unknow'
    //     this.gender = 'unknow'
    // }
    // 類方法
    printName(){
        console.log(this.name)
    }
}
// 類繼承
class Employee extends Person02{
    constructor(name, age, gender, department){
        super(name, age, gender)
        this.department = department
    }
    printDepartment(){
        console.log(this.department)
    }
}
let person02 = new Person02('Mickey', '123', 'female')
person02.printName()
let person03 = new Employee('Jack', '223', 'male', 'PG')
person03.printName()
person03.printDepartment()
```

## 🏖JSON新應用
- 屬性名和值名稱一樣可以省略
	```javascript
	// 屬性名和值名稱一樣可以省略
	let name = 'Mickey'
	let mickeyAge = 123
	let obj01 = {name: name, age: mickeyAge, gender: 'male'}
	let obj02 = {
	    name,
	    age: mickeyAge, // 屬性名和值的變量名稱不一樣就不能省洛
	    gender: 'male',
	    printName(){ // 定義函數，可省略function關鍵字
	        console.log(name)
	    },
	}
	obj02.printName()
	```
- `JSON.stringify()`，串行化
	```javascript
	let obj03 = {name: 'jack', age: 223, gender: 'male'}
	// 串行化
	let jsonStr = JSON.stringify(obj03)
	console.log(jsonStr) // {"name":"jack","age":223,"gender":"male"}
	```
- `JSON.parse()`，反串行化
	```javascript
	// 反串行化
	let obj04 = JSON.parse(jsonStr)
	console.log(obj04.name)
	```

## 🏖Module模塊化編程
- 模塊化優點
	1. 避免變量、函數重名導致的錯誤
	2. 避免引入時的層層依賴
	3. 可提升執行效率

### export
`export`，用於規定模塊對外接口

1. 一次指定所有的對外接口
	```javascript
	// T037_Moduel01.js
	let name = 'Mickey'
	let id = 123
	
	function printName(){
		console.log(name)
	}
	
	function add(a, b){
		console.log(a + b)
	}
	
	// 指定暴露的變量、函數
	export {name, id, printName, add}
	```
2. 在變量、函數前指定此接口是否對外
	```javascript
	// T037_Moduel02.js
	// 指定暴露的變量、函數
	export let name = 'Jack'
	let id = 223
	
	export function printName(){
		console.log(name)
	}
	
	export function printId(){
		console.log(id)
	}
	
	export class Employee{
		constructor(){}
	
		doSomething(){
			console.log("do something...")
		}
	}
	```
3. `export default`，由導入方決定函數名，但一個模塊只能使用一次
	```javascript
	// T037_Moduel03.js
	
	let name = 'Marry'
	let id = 323
	
	// 由導入方決定函數名，但一個模塊只能有一個export default
	export default function(args){
		console.log(args)
	}
	```

### import
`import`，用於引入其他模塊的功能

1. 使用模塊定義的變量名、函數名
	```html
	<!-- 在html引入模塊要用type="module" -->
	<script type="module">
		// 使用模塊定義的變量名、函數名
		import {name, id, printName} from './T037_Moduel01.js'
		console.log(name)
		printName()
	</script>
	```
2. 如果引入重名的變量或函數，`as`指定別名
	```html
	<script type="module">
		// 如果引入重名的變量或函數，指定別名
		import {name as jackName, printName as printJack, printId, Employee} from './T037_Moduel02.js'
		console.log(jackName) // 使用別名調用變量
		printJack() // 使用別名調用函數
		let employee = new Employee()
		employee.doSomething()
	</script>
	```
3. 導入`export default`，指定名稱
	```html
	<script type="module">
		// 導入export default，指定名稱
		import printArgs from './T037_Moduel03.js'
		printArgs([1, 2, 3, 4])
	</script>
	```
4. 將模塊所有內容導入
	```html
	<script type="module">
		// 將模塊所有內容導入
		import * as test from './T037_Moduel01.js'
		console.log(test.name)
		test.printName()
	</script>
	```

## 🏖Promise
- Promise主要用於異步計算，它可以將異步操作隊列化，並按期望的順序執行

### ES6前異步用法
ES6前處理多層異步請求，很容易陷入回調地獄，導致程式碼層次過多

```javascript
// ES5，回調地獄，之一
$.get('url01', data1 => {
	// do something...
	$.get('url02', data2 => {
		// do something...
		$.get('url03', data3 =>{
			// do something...
		})
	})
})

// ES5，回調地獄，之二
setTimeout(() => {
	console.log('first step')
	setTimeout(() => {
		console.log('second step')
		setTimeout(() => {
			console.log('third step')
		}, 1000)
	}, 1000)
}, 1000)
```

### ES6異步用法
ES6 Promise可以解決程式碼層次過多的問題
```javascript
// ES6 Promise用法
const promise01 = new Promise((reslove, reject) => {
	console.log('first step')
	// // 執行.then第一個函數，狀態為成功
	// reslove('success')
	// // 執行.then第二個函數，狀態為拒絕
	// reject('error')
	// 執行catch函數，表示出現異常
	throw 'test exception'
})
.then(
	reslove => { // 成功時調用
		console.log('reslove message : ' + reslove)
		return 'sucess = first' // 返回做為下一個then的參數
	},
	reject => { // 拒絕時調用
		console.log('reject message : ' + reject)
		return 'error = first'
	}
).catch(err =>{ // 異常時的處理
	console.log("have some error : " + error)
})

// Promise可作為變量做後續處理
promise01.then(reslove =>{
		console.log('second step')
		console.log('reslove message : ' + reslove)
	},
	reject => {
		console.log('second step')
		console.log('reject message : ' + reject)
	}
)

// 無限定狀態都會調用，並按順序執行
promise01.then(function(){
	console.log('third step')
}).then(function(){
	console.log('fourth step')
})

```

### ES6處理并發
```javascript
// 并發請求
Promise.all([
	new Promise((resolve, reject) =>{
		// do something
		resolve('first request')
	}),
	new Promise((resolve, reject) =>{
		// do something
		resolve('second request')
	}),
]).then( // 包含所有并發請求的信息
	resolve => {
		console.log(resolve) // ['first request', 'second request']
	}
)
```
